示例#1
0
class Poll(TranslatableModel):
    # Add this hack because of broken BooleanField in MySQL when using hvad app
    PUBLISH_CHOICES = (
        ('T', 'True'),
        ('F', 'False'),
    )

    date = models.DateField(auto_now_add=True)
    is_published = models.CharField(max_length=10, default='True', verbose_name=_('is published'), choices=PUBLISH_CHOICES)

    translations = TranslatedFields(
        title = models.CharField(max_length=250, verbose_name=_('question')),
    )

    objects = TranslationManager()
    published = PublishedManager()

    class Meta:
        ordering = ['-date']
        verbose_name = _('poll')
        verbose_name_plural = _('polls')

    def __unicode__(self):
        return self.title
        
    def get_vote_count(self):
        return Vote.objects.filter(poll=self).count()
    vote_count = property(fget=get_vote_count)
    
    def get_cookie_name(self):
        return str('poll_%s' % (self.pk)) 
示例#2
0
class Newsletter(TranslatableModel):

    name = models.CharField(max_length=256, verbose_name=_('Name (internal)'))

    translations = TranslatedFields(
        title=models.CharField(max_length=256,
                               verbose_name=_('Title (translated)')),
        description=models.TextField(blank=True,
                                     null=True,
                                     verbose_name=_('Description')),
    )

    backend = models.CharField(max_length=36,
                               default='mailchimp',
                               choices=BACKEND_CHOICES)
    backend_id = models.CharField(max_length=64, blank=True, null=True)
    backend_api_key = models.CharField(max_length=64, blank=True, null=True)

    objects = TranslationManager()

    class Meta:
        app_label = 'subscription'
        verbose_name = _('Newsletter')
        #ordering = ('-publish',)

    def __unicode__(self):
        return u'%s' % self.lazy_translation_getter('name', str(self.pk))

    def get_backend(self):

        backend = None
        if self.backend == 'mailchimp':
            if self.backend_api_key:
                backend = MailchimpBackend(api_key=self.backend_api_key)
            else:
                backend = MailchimpBackend()
        return backend

    def subscribe(self, email, name, language=None, channel=None):
        log.info(u'subscribe %s - %s [%s] %s on %s' %
                 (email, name, language, channel, self.name))
        backend = self.get_backend()
        backend.subscribe(list_id=self.backend_id,
                          email=email,
                          name=name,
                          language=language,
                          channel=channel)

    def unsubscribe(self, email):
        log.info(u'unsubscribe %s from %s' % (email, self.name))
        backend = self.get_backend()
        backend.unsubscribe(list_id=self.backend_id, email=email)

    def save(self, *args, **kwargs):
        super(Newsletter, self).save(*args, **kwargs)
示例#3
0
class EventCategory(TranslatableModel):
    on_site = CurrentSiteManager(field_name='sites')
    translations = TranslatedFields(
        name=models.CharField(_(u"Name"), max_length=100))
    slug = models.SlugField(
        _('Slug'),
        help_text=
        _('Name in lowercase with no spaces which will be chown in the URL of the navigator'
          ))
    sites = models.ManyToManyField(Site)
    created = models.DateTimeField(auto_now_add=True, editable=False)
    updated = models.DateTimeField(auto_now=True, editable=False)

    objects = TranslationManager()

    def site_list(self):
        return self.sites.all()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name_plural = _(u'Termin Kategorien')
        verbose_name = _(u'Termin Kategorie')
示例#4
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        for key, value in kwargs.items():
            if key in self._translated_field_names and not key in veto_names:
                tkwargs[key] = value
            else:
                skwargs[key] = value

        super(TranslatableModel, self).__init__(*args, **skwargs)

        # Create a translation if there are translated fields
        if tkwargs:
            tkwargs['language_code'] = tkwargs.get(
                'language_code') or get_language()
            set_cached_translation(self,
                                   self._meta.translations_model(**tkwargs))

    @classmethod
    def save_translations(cls, instance, **kwargs):
        'Signal handler for post_save'
        translation = get_cached_translation(instance)
        if translation is not None:
            translation.master = instance
            translation.save()

    def translate(self, language_code):
        ''' Create a new translation for current instance.
            Does NOT check if the translation already exists!
        '''
        set_cached_translation(
            self, self._meta.translations_model(language_code=language_code))
        return self

    def safe_translation_getter(self, name, default=None):
        cache = get_cached_translation(self)
        if cache is None:
            return default
        return getattr(cache, name, default)

    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(),
                     settings.LANGUAGE_CODE) + FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        set_cached_translation(self, translation)
        return getattr(translation, name, default)

    def get_available_languages(self):
        """ Get a list of all available language_code in db. """
        qs = getattr(self, self._meta.translations_accessor).all()
        if qs._result_cache is not None:
            return [obj.language_code for obj in qs]
        return qs.values_list('language_code', flat=True)

    #===========================================================================
    # Validation
    #===========================================================================

    def clean_fields(self, exclude=None):
        super(TranslatableModel, self).clean_fields(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.clean_fields(
                exclude=exclude +
                ['id', 'master', 'master_id', 'language_code'])

    def validate_unique(self, exclude=None):
        super(TranslatableModel, self).validate_unique(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.validate_unique(exclude=exclude)

    #===========================================================================
    # Checks - require Django 1.7 or newer
    #===========================================================================

    if django.VERSION >= (1, 7):

        @classmethod
        def check(cls, **kwargs):
            errors = super(TranslatableModel, cls).check(**kwargs)
            errors.extend(cls._check_shared_translated_clash())
            return errors

        @classmethod
        def _check_shared_translated_clash(cls):
            fields = set(
                chain.from_iterable(
                    (f.name, f.attname) for f in cls._meta.fields))
            tfields = set(
                chain.from_iterable(
                    (f.name, f.attname)
                    for f in cls._meta.translations_model._meta.fields
                    if f.name not in ('id', 'master')))
            return [
                checks.Error(
                    "translated field '%s' clashes with untranslated field." %
                    field,
                    hint=None,
                    obj=cls,
                    id='hvad.models.E01')
                for field in tfields.intersection(fields)
            ]

        @classmethod
        def _check_local_fields(cls, fields, option):
            """ Remove fields we recognize as translated fields from tests """
            to_check = []
            for field in fields:
                try:
                    cls._meta.translations_model._meta.get_field(field)
                except FieldDoesNotExist:
                    to_check.append(field)
            return super(TranslatableModel,
                         cls)._check_local_fields(to_check, option)

        @classmethod
        def _check_ordering(cls):
            if not cls._meta.ordering:
                return []

            if not isinstance(cls._meta.ordering, (list, tuple)):
                return [
                    checks.Error("'ordering' must be a tuple or list.",
                                 hint=None,
                                 obj=cls,
                                 id='models.E014')
                ]

            fields = [f for f in cls._meta.ordering if f != '?']
            fields = [f[1:] if f.startswith('-') else f for f in fields]
            fields = set(f for f in fields
                         if f not in ('_order', 'pk') and '__' not in f)

            valid_fields = set(
                chain.from_iterable(
                    (f.name, f.attname) for f in cls._meta.fields))
            valid_tfields = set(
                chain.from_iterable(
                    (f.name, f.attname)
                    for f in cls._meta.translations_model._meta.fields
                    if f.name not in ('master', 'language_code')))

            return [
                checks.Error(
                    "'ordering' refers to the non-existent field '%s' --hvad."
                    % field,
                    hint=None,
                    obj=cls,
                    id='models.E015')
                for field in fields - valid_fields - valid_tfields
            ]

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            opts = self._meta.translations_model._meta
            result = set()

            if django.VERSION >= (1, 8):
                for field in opts.get_fields():
                    result.add(field.name)
                    if hasattr(field, 'attname'):
                        result.add(field.attname)
            else:
                result = set(opts.get_all_field_names())
                for name in tuple(result):
                    try:
                        attname = opts.get_field(name).get_attname()
                    except (FieldDoesNotExist, AttributeError):
                        continue
                    if attname:
                        result.add(attname)

            self._translated_field_names_cache = tuple(result)
        return self._translated_field_names_cache
示例#5
0
 class InvalidModel2(object):
     objects = TranslationManager()
示例#6
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        for key, value in kwargs.items():
            if key in self._translated_field_names and not key in veto_names:
                tkwargs[key] = value
            else:
                skwargs[key] = value

        super(TranslatableModel, self).__init__(*args, **skwargs)

        # Create a translation if there are translated fields
        if tkwargs:
            tkwargs['language_code'] = tkwargs.get(
                'language_code') or get_language()
            set_cached_translation(self,
                                   self._meta.translations_model(**tkwargs))

    @classmethod
    def save_translations(cls, instance, **kwargs):
        'Signal handler for post_save'
        translation = get_cached_translation(instance)
        if translation is not None:
            translation.master = instance
            translation.save()

    def translate(self, language_code):
        ''' Create a new translation for current instance.
            Does NOT check if the translation already exists!
        '''
        set_cached_translation(
            self, self._meta.translations_model(language_code=language_code))
        return self

    def safe_translation_getter(self, name, default=None):
        cache = get_cached_translation(self)
        if cache is None:
            return default
        return getattr(cache, name, default)

    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(),
                     settings.LANGUAGE_CODE) + FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        set_cached_translation(self, translation)
        return getattr(translation, name, default)

    def get_available_languages(self):
        """ Get a list of all available language_code in db. """
        qs = getattr(self, self._meta.translations_accessor).all()
        if qs._result_cache is not None:
            return [obj.language_code for obj in qs]
        return qs.values_list('language_code', flat=True)

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            opts = self._meta.translations_model._meta
            result = set()

            if django.VERSION >= (1, 8):
                for field in opts.get_fields():
                    result.add(field.name)
                    if hasattr(field, 'attname'):
                        result.add(field.attname)
            else:
                result = set(opts.get_all_field_names())
                for name in tuple(result):
                    try:
                        attname = opts.get_field(name).get_attname()
                    except (FieldDoesNotExist, AttributeError):
                        continue
                    if attname:
                        result.add(attname)

            self._translated_field_names_cache = tuple(result)
        return self._translated_field_names_cache
示例#7
0
class Event(TranslatableModel):
    on_site = EventManager(field_name='sites')
    translations = TranslatedFields(name=models.CharField(
        _("Terminname"),
        max_length=250,
        help_text=_(u'Zum Beispiel: "Drittes Sommerfest"')))
    slug = models.SlugField(
        _('Kurzname'),
        unique_for_date='start',
        help_text=
        _('Name in Kleinbuchstaben ohne Leerzeichen der in der URL verwendet wird.'
          ))
    description = PlaceholderField(
        'event_description',
        blank=True,
        null=True,
        verbose_name=_('Beschreibung'),
    )

    start = models.DateField(
        _(u"Datum"),
        help_text=
        _(u'Format: jjjj-mm-tt. Wann findet der Termin statt? Wenn der Termin über mehrere Tage stattfindet dann gib ein Startdatum und unten ein Enddatum an.'
          ))
    time = models.CharField(
        _("Zeitpunkt"),
        blank=True,
        max_length=100,
        help_text=
        _(u'* Optional.  zum Beispiel: "8", "7 - 8" oder "7:30" das "Uhr" wird automatisch hinzugefügt'
          ))
    # Todo -- continue to validate that the end is after the start?
    end = models.DateField(
        _(u'Enddatum'),
        blank=True,
        null=True,
        help_text=
        _(u'* Optional.  Wenn der Termin über mehrere Tage andauert dann gebe hier ein Enddatum an. Wenn freigelassen wird automatisch das Startdatum eingetragen.'
          ))
    categories = models.ManyToManyField(
        EventCategory,
        blank=True,
        null=True,
        limit_choices_to={'sites__id': settings.SITE_ID})
    sites = models.ManyToManyField(Site,
                                   editable=False,
                                   default=[settings.SITE_ID])

    created = models.DateTimeField(auto_now_add=True, editable=False)
    updated = models.DateTimeField(auto_now=True, editable=False)

    objects = TranslationManager()

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return ('event_detail', None, {
            'year': self.start.strftime("%Y"),
            'month': self.start.strftime("%m"),
            'day': self.start.strftime("%d"),
            'slug': self.slug,
        })

    def has_passed(self):
        if self.end:
            return self.end < date.today()
        else:
            return self.start < date.today()

    def category_list(self):
        if self.categories.all():
            return ", ".join([c.name for c in self.categories.all()])
        return _(u'(No categories)')

    def site_list(self):
        return self.sites.all()

    def is_mutiple_days(self):
        return self.end is not None and self.end > self.start

    def get_next_upcoming(self):
        try:
            return Event.on_site.upcoming().filter(
                start__gte=self.start).exclude(id=self.id)[0]
        except IndexError:
            return None

    def get_previous_upcoming(self):
        try:
            return Event.on_site.upcoming().filter(
                start__lte=self.start).exclude(id=self.id)[0]
        except IndexError:
            return None

    def date_span(self):
        if self.end:
            s = "%s &mdash; %s" % (format(self.start), format(self.end))
        else:
            s = format(self.start)
        return mark_safe(s)

    date_span.short_description = 'date'
    date_span.admin_order_field = 'start'
    date_span.allow_tags = True

    class Meta:
        ordering = ('-start', )
        verbose_name = _(u'Termin')
        verbose_name_plural = _(u'Termine')
示例#8
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    __metaclass__ = TranslatableModelBase

    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        tkwargs = {}  # translated fields
        skwargs = {}  # shared fields

        if 'master' in kwargs.keys():
            raise RuntimeError(
                    "Cannot init  %s class with a 'master' argument" % \
                    self.__class__.__name__
            )

        # filter out all the translated fields (including 'master' and 'language_code')
        primary_key_names = ('pk', self._meta.pk.name)
        for key in kwargs.keys():
            if key in self._translated_field_names:
                if not key in primary_key_names:
                    # we exclude the pk of the shared model
                    tkwargs[key] = kwargs.pop(key)
        if not tkwargs.keys():
            # if there where no translated options, then we assume this is a
            # regular init and don't want to do any funky stuff
            super(TranslatableModel, self).__init__(*args, **kwargs)
            return

        # there was at least one of the translated fields (or a language_code)
        # in kwargs. We need to do magic.
        # extract all the shared fields (including the pk)
        for key in kwargs.keys():
            if key in self._shared_field_names:
                skwargs[key] = kwargs.pop(key)
        # do the regular init minus the translated fields
        super(TranslatableModel, self).__init__(*args, **skwargs)
        # prepopulate the translations model cache with an translation model
        tkwargs['language_code'] = tkwargs.get('language_code', get_language())
        tkwargs['master'] = self
        translated = self._meta.translations_model(*args, **tkwargs)
        setattr(self, self._meta.translations_cache, translated)

    @classmethod
    def contribute_translations(cls, rel):
        """
        Contribute translations options to the inner Meta class and set the
        descriptors.
        
        This get's called from TranslatableModelBase.__new__
        """
        opts = cls._meta
        opts.translations_accessor = rel.get_accessor_name()
        opts.translations_model = rel.model
        opts.translations_cache = '%s_cache' % rel.get_accessor_name()
        trans_opts = opts.translations_model._meta

        # Set descriptors
        ignore_fields = [
            'pk',
            'master',
            opts.translations_model._meta.pk.name,
        ]
        for field in trans_opts.fields:
            if field.name in ignore_fields:
                continue
            if field.name == 'language_code':
                attr = LanguageCodeAttribute(opts)
            else:
                attr = TranslatedAttribute(opts, field.name)
            setattr(cls, field.name, attr)

    @classmethod
    def save_translations(cls, instance, **kwargs):
        """
        When this instance is saved, also save the (cached) translation
        """
        opts = cls._meta
        if hasattr(instance, opts.translations_cache):
            trans = getattr(instance, opts.translations_cache)
            if not trans.master_id:
                trans.master = instance
            trans.save()

    def translate(self, language_code):
        """
        Returns an Model instance in the specified language.
        Does NOT check if the translation already exists!
        Does NOT interact with the database.
        
        This will refresh the translations cache attribute on the instance.
        """
        tkwargs = {
            'language_code': language_code,
            'master': self,
        }
        translated = self._meta.translations_model(**tkwargs)
        setattr(self, self._meta.translations_cache, translated)
        return self

    def safe_translation_getter(self, name, default=None):
        cache = getattr(self, self._meta.translations_cache, None)
        if not cache:
            return default
        return getattr(cache, name, default)

    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        cache = getattr(self, self._meta.translations_cache, NoTranslation)
        trans = self._meta.translations_model.objects.filter(
            master__pk=self.pk)
        if not cache and cache != NoTranslation and not trans.exists(
        ):  # check if there is no translations
            return default
        elif getattr(
                cache, name, NoTranslation) == NoTranslation and trans.exists(
                ):  # We have translations, but no specific translation cached
            trans_in_own_language = trans.filter(language_code=get_language())
            if trans_in_own_language.exists():
                trans = trans_in_own_language[0]
            else:
                trans = trans[0]
            setattr(self, self._meta.translations_cache, trans)
            return getattr(trans, name)
        return getattr(cache, name)

    def get_available_languages(self):
        manager = self._meta.translations_model.objects
        return manager.filter(master=self).values_list('language_code',
                                                       flat=True)

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _shared_field_names(self):
        if getattr(self, '_shared_field_names_cache', None) is None:
            self._shared_field_names_cache = self._meta.get_all_field_names()
        return self._shared_field_names_cache

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            self._translated_field_names_cache = self._meta.translations_model._meta.get_all_field_names(
            )
        return self._translated_field_names_cache
示例#9
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    objects = TranslationManager()
    _plain_manager = models.Manager()

    class Meta:
        abstract = True
        if django.VERSION >= (1, 10):
            base_manager_name = '_plain_manager'

    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        translations_opts = self._meta.translations_model._meta
        for key, value in kwargs.items():
            if key in veto_names:
                skwargs[key] = value
            else:
                try:
                    translations_opts.get_field(key)
                except FieldDoesNotExist:
                    skwargs[key] = value
                else:
                    tkwargs[key] = value
        super(TranslatableModel, self).__init__(*args, **skwargs)
        if tkwargs:
            tkwargs['language_code'] = tkwargs.get(
                'language_code') or get_language()
            set_cached_translation(self,
                                   self._meta.translations_model(**tkwargs))

    def save(self, *args, **skwargs):
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        translations_opts = self._meta.translations_model._meta
        translation = get_cached_translation(self)
        tkwargs = skwargs.copy()

        # split update_fields in shared/translated fields
        update_fields = skwargs.get('update_fields')
        if update_fields is not None:
            supdate, tupdate = [], []
            for name in update_fields:
                if name in veto_names:
                    supdate.append(name)
                else:
                    try:
                        translations_opts.get_field(name)
                    except FieldDoesNotExist:
                        supdate.append(name)
                    else:
                        tupdate.append(name)
            skwargs['update_fields'], tkwargs[
                'update_fields'] = supdate, tupdate

        # save share and translated model in a single transaction
        if update_fields is None or skwargs['update_fields']:
            super(TranslatableModel, self).save(*args, **skwargs)
        if (update_fields is None
                or tkwargs['update_fields']) and translation is not None:
            if translation.pk is None and update_fields:
                del tkwargs['update_fields']  # allow new translations
            translation.master = self
            translation.save(*args, **tkwargs)

    save.alters_data = True

    def translate(self, language_code):
        ''' Create a new translation for current instance.
            Does NOT check if the translation already exists!
        '''
        set_cached_translation(
            self, self._meta.translations_model(language_code=language_code))
        return self

    translate.alters_data = True

    def safe_translation_getter(self, name, default=None):
        cache = get_cached_translation(self)
        if cache is None:
            return default
        return getattr(cache, name, default)

    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(), ) + hvad_settings.FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        set_cached_translation(self, translation)
        return getattr(translation, name, default)

    def get_available_languages(self):
        """ Get a list of all available language_code in db. """
        qs = getattr(self, self._meta.translations_accessor).all()
        if qs._result_cache is not None:
            return [obj.language_code for obj in qs]
        return qs.values_list('language_code', flat=True)

    #===========================================================================
    # Validation
    #===========================================================================

    def clean_fields(self, exclude=None):
        super(TranslatableModel, self).clean_fields(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.clean_fields(
                exclude=exclude +
                ['id', 'master', 'master_id', 'language_code'])

    def validate_unique(self, exclude=None):
        super(TranslatableModel, self).validate_unique(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.validate_unique(exclude=exclude)

    #===========================================================================
    # Checks
    #===========================================================================

    @classmethod
    def check(cls, **kwargs):
        errors = super(TranslatableModel, cls).check(**kwargs)
        errors.extend(cls._check_shared_translated_clash())
        errors.extend(cls._check_default_manager_translation_aware())
        return errors

    @classmethod
    def _check_shared_translated_clash(cls):
        fields = set(
            chain.from_iterable((f.name, f.attname) for f in cls._meta.fields))
        tfields = set(
            chain.from_iterable(
                (f.name, f.attname)
                for f in cls._meta.translations_model._meta.fields
                if f.name not in ('id', 'master')))
        return [
            checks.Error(
                "translated field '%s' clashes with untranslated field." %
                field,
                hint=None,
                obj=cls,
                id='hvad.models.E01') for field in tfields.intersection(fields)
        ]

    @classmethod
    def _check_default_manager_translation_aware(cls):
        errors = []
        if not isinstance(cls._default_manager, TranslationManager):
            errors.append(
                checks.Error(
                    "The default manager on a TranslatableModel must be a "
                    "TranslationManager instance or an instance of a subclass of "
                    "TranslationManager, the default manager of %r is not." %
                    cls,
                    hint=None,
                    obj=cls,
                    id='hvad.models.E02'))
        return errors

    @classmethod
    def _check_local_fields(cls, fields, option):
        """ Remove fields we recognize as translated fields from tests """
        to_check = []
        for field in fields:
            try:
                cls._meta.translations_model._meta.get_field(field)
            except FieldDoesNotExist:
                to_check.append(field)
        return super(TranslatableModel,
                     cls)._check_local_fields(to_check, option)

    @classmethod
    def _check_ordering(cls):
        if not cls._meta.ordering:
            return []

        if not isinstance(cls._meta.ordering, (list, tuple)):
            return [
                checks.Error("'ordering' must be a tuple or list.",
                             hint=None,
                             obj=cls,
                             id='models.E014')
            ]

        fields = [f for f in cls._meta.ordering if f != '?']
        fields = [f[1:] if f.startswith('-') else f for f in fields]
        fields = set(f for f in fields
                     if f not in ('_order', 'pk') and '__' not in f)

        valid_fields = set(
            chain.from_iterable((f.name, f.attname) for f in cls._meta.fields))
        valid_tfields = set(
            chain.from_iterable(
                (f.name, f.attname)
                for f in cls._meta.translations_model._meta.fields
                if f.name not in ('master', 'language_code')))

        return [
            checks.Error(
                "'ordering' refers to the non-existent field '%s' --hvad." %
                field,
                hint=None,
                obj=cls,
                id='models.E015')
            for field in fields - valid_fields - valid_tfields
        ]
示例#10
0
class License(TranslatableModel, MigrationMixin, UUIDModelMixin,
              TimestampedModelMixin, models.Model):

    slug = models.SlugField(max_length=100, unique=False)
    name = models.CharField(max_length=200)
    key = models.CharField(verbose_name=_("License key"),
                           max_length=36,
                           blank=True,
                           null=True,
                           help_text=_("used e.g. for the icon-names"))
    restricted = models.NullBooleanField(null=True, blank=True)
    version = models.CharField(verbose_name=_("License version"),
                               max_length=36,
                               blank=True,
                               null=True,
                               help_text=_("e.g. 2.5 CH"))
    iconset = models.CharField(verbose_name=_("Iconset"),
                               max_length=36,
                               blank=True,
                               null=True,
                               help_text=_("e.g. cc-by, cc-nc, cc-sa"))
    link = models.URLField(null=True, blank=True)
    parent = models.ForeignKey('self',
                               null=True,
                               blank=True,
                               related_name='license_children')
    is_default = models.NullBooleanField(default=False, null=True, blank=True)
    selectable = models.NullBooleanField(default=True, null=True, blank=True)
    is_promotional = models.NullBooleanField(default=False,
                                             null=True,
                                             blank=True)

    translations = TranslatedFields(
        name_translated=models.CharField(max_length=200),
        excerpt=models.TextField(blank=True, null=True),
        license_text=models.TextField(blank=True, null=True))

    objects = TranslationManager()

    class Meta:
        app_label = 'alibrary'
        verbose_name = _('License')
        verbose_name_plural = _('Licenses')
        ordering = (
            'parent__name',
            'name',
        )

    def __unicode__(self):
        if self.parent:
            return '%s - %s' % (self.parent.name, self.name)
        else:
            return '%s' % (self.name)

    @property
    def title(self):
        if self.parent:
            return '{} - {}'.format(self.parent.name, self.name)
        return self.name

    def get_absolute_url(self):
        return reverse('alibrary-license-detail', args=(self.slug, ))

    def get_admin_url(self):
        return reverse("admin:alibrary_license_change", args=(self.pk, ))

    @property
    def iconset_display(self):
        from django.utils.html import mark_safe
        html = ''
        if self.iconset:
            icons = self.iconset.split(',')
            for icon in icons:
                html += '<i class="icon icon-license-%s"></i>' % icon.strip(
                    ' ')

        return mark_safe(html)
示例#11
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    objects = TranslationManager()
    _plain_manager = models.Manager()

    class Meta:
        abstract = True
        base_manager_name = '_plain_manager'

    def __init__(self, *args, **kwargs):
        # Split arguments into shared/translatd
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        skwargs, tkwargs = {}, {}
        translations_opts = self._meta.translations_model._meta
        for key, value in kwargs.items():
            if key in veto_names:
                skwargs[key] = value
            else:
                try:
                    translations_opts.get_field(key)
                except FieldDoesNotExist:
                    skwargs[key] = value
                else:
                    tkwargs[key] = value
        super(TranslatableModel, self).__init__(*args, **skwargs)
        language_code = tkwargs.get('language_code') or get_language()
        if language_code is not NoTranslation:
            tkwargs['language_code'] = language_code
            set_cached_translation(self,
                                   self._meta.translations_model(**tkwargs))

    @classmethod
    def from_db(cls, db, field_names, values):
        if len(values) != len(cls._meta.concrete_fields):
            # Missing values are deferred and must be marked as such
            values = list(values)
            values.reverse()
            values = [
                values.pop() if f.attname in field_names else models.DEFERRED
                for f in cls._meta.concrete_fields
            ]
        new = cls(*values, language_code=NoTranslation)
        new._state.adding = False
        new._state.db = db
        return new

    def save(self, *args, **skwargs):
        veto_names = ('pk', 'master', 'master_id',
                      self._meta.translations_model._meta.pk.name)
        translations_opts = self._meta.translations_model._meta
        translation = get_cached_translation(self)
        tkwargs = skwargs.copy()

        # split update_fields in shared/translated fields
        update_fields = skwargs.get('update_fields')
        if update_fields is not None:
            supdate, tupdate = [], []
            for name in update_fields:
                if name in veto_names:
                    supdate.append(name)
                else:
                    try:
                        translations_opts.get_field(name)
                    except FieldDoesNotExist:
                        supdate.append(name)
                    else:
                        tupdate.append(name)
            skwargs['update_fields'], tkwargs[
                'update_fields'] = supdate, tupdate

        # save share and translated model in a single transaction
        db = router.db_for_write(self.__class__, instance=self)
        with transaction.atomic(using=db, savepoint=False):
            if update_fields is None or skwargs['update_fields']:
                super(TranslatableModel, self).save(*args, **skwargs)
            if (update_fields is None
                    or tkwargs['update_fields']) and translation is not None:
                if translation.pk is None and update_fields:
                    del tkwargs['update_fields']  # allow new translations
                translation.master = self
                translation.save(*args, **tkwargs)

    save.alters_data = True

    def translate(self, language_code):
        """ Create a new translation for current instance.
            Does NOT check if the translation already exists.
        """
        set_cached_translation(
            self, self._meta.translations_model(language_code=language_code))

    translate.alters_data = True

    #===========================================================================
    # Validation
    #===========================================================================

    def clean_fields(self, exclude=None):
        super(TranslatableModel, self).clean_fields(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.clean_fields(
                exclude=exclude +
                ['id', 'master', 'master_id', 'language_code'])

    def validate_unique(self, exclude=None):
        super(TranslatableModel, self).validate_unique(exclude=exclude)
        translation = get_cached_translation(self)
        if translation is not None:
            translation.validate_unique(exclude=exclude)

    #===========================================================================
    # Checks
    #===========================================================================

    @classmethod
    def check(cls, **kwargs):
        errors = super(TranslatableModel, cls).check(**kwargs)
        errors.extend(cls._check_shared_translated_clash())
        errors.extend(cls._check_default_manager_translation_aware())
        return errors

    @classmethod
    def _check_shared_translated_clash(cls):
        fields = set(
            chain.from_iterable((f.name, f.attname) for f in cls._meta.fields))
        tfields = set(
            chain.from_iterable(
                (f.name, f.attname)
                for f in cls._meta.translations_model._meta.fields
                if f.name not in ('id', 'master')))
        return [
            checks.Error(
                "translated field '%s' clashes with untranslated field." %
                field,
                hint=None,
                obj=cls,
                id='hvad.models.E01') for field in tfields.intersection(fields)
        ]

    @classmethod
    def _check_default_manager_translation_aware(cls):
        errors = []
        if not isinstance(cls._default_manager, TranslationManager):
            errors.append(
                checks.Error(
                    "The default manager on a TranslatableModel must be a "
                    "TranslationManager instance or an instance of a subclass of "
                    "TranslationManager, the default manager of %r is not." %
                    cls,
                    hint=None,
                    obj=cls,
                    id='hvad.models.E02'))
        return errors

    @classmethod
    def _check_local_fields(cls, fields, option):
        """ Remove fields we recognize as translated fields from tests """
        to_check = []
        for field in fields:
            try:
                cls._meta.translations_model._meta.get_field(field)
            except FieldDoesNotExist:
                to_check.append(field)
        return super(TranslatableModel,
                     cls)._check_local_fields(to_check, option)

    @classmethod
    def _check_ordering(cls):
        if not cls._meta.ordering:
            return []

        if not isinstance(cls._meta.ordering, (list, tuple)):
            return [
                checks.Error("'ordering' must be a tuple or list.",
                             hint=None,
                             obj=cls,
                             id='models.E014')
            ]

        fields = [f for f in cls._meta.ordering if f != '?']
        fields = [f[1:] if f.startswith('-') else f for f in fields]
        fields = set(f for f in fields
                     if f not in ('_order', 'pk') and '__' not in f)

        valid_fields = set(
            chain.from_iterable((f.name, f.attname) for f in cls._meta.fields))
        valid_tfields = set(
            chain.from_iterable(
                (f.name, f.attname)
                for f in cls._meta.translations_model._meta.fields
                if f.name not in ('master', 'language_code')))

        return [
            checks.Error(
                "'ordering' refers to the non-existent field '%s' --hvad." %
                field,
                hint=None,
                obj=cls,
                id='models.E015')
            for field in fields - valid_fields - valid_tfields
        ]
示例#12
0
class TranslatableModel(models.Model):
    """
    Base model for all models supporting translated fields (via TranslatedFields).
    """
    # change the default manager to the translation manager
    objects = TranslationManager()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        tkwargs = {}  # translated fields
        skwargs = {}  # shared fields

        if 'master' in kwargs.keys():
            raise RuntimeError(
                    "Cannot init  %s class with a 'master' argument" % \
                    self.__class__.__name__
            )

        # filter out all the translated fields (including 'master' and 'language_code')
        primary_key_names = ('pk', self._meta.pk.name)
        for key in list(kwargs.keys()):
            if key in self._translated_field_names:
                if not key in primary_key_names:
                    # we exclude the pk of the shared model
                    tkwargs[key] = kwargs.pop(key)
        if not tkwargs.keys():
            # if there where no translated options, then we assume this is a
            # regular init and don't want to do any funky stuff
            super(TranslatableModel, self).__init__(*args, **kwargs)
            return

        # there was at least one of the translated fields (or a language_code)
        # in kwargs. We need to do magic.
        # extract all the shared fields (including the pk)
        for key in list(kwargs.keys()):
            if key in self._shared_field_names:
                skwargs[key] = kwargs.pop(key)
        # do the regular init minus the translated fields
        super(TranslatableModel, self).__init__(*args, **skwargs)
        # prepopulate the translations model cache with an translation model
        tkwargs['language_code'] = tkwargs.get('language_code', get_language())
        tkwargs['master'] = self
        translated = self._meta.translations_model(*args, **tkwargs)
        setattr(self, self._meta.translations_cache, translated)

    @classmethod
    def save_translations(cls, instance, **kwargs):
        """
        When this instance is saved, also save the (cached) translation
        """
        opts = cls._meta
        if hasattr(instance, opts.translations_cache):
            trans = getattr(instance, opts.translations_cache)
            if not trans.master_id:
                trans.master = instance
            trans.save()

    def translate(self, language_code):
        """
        Returns an Model instance in the specified language.
        Does NOT check if the translation already exists!
        Does NOT interact with the database.
        
        This will refresh the translations cache attribute on the instance.
        """
        tkwargs = {
            'language_code': language_code,
            'master': self,
        }
        translated = self._meta.translations_model(**tkwargs)
        setattr(self, self._meta.translations_cache, translated)
        return self

    def safe_translation_getter(self, name, default=None):
        cache = getattr(self, self._meta.translations_cache, None)
        if not cache:
            return default
        return getattr(cache, name, default)

    def lazy_translation_getter(self, name, default=None):
        """
        Lazy translation getter that fetches translations from DB in case the instance is currently untranslated and
        saves the translation instance in the translation cache
        """
        stuff = self.safe_translation_getter(name, NoTranslation)
        if stuff is not NoTranslation:
            return stuff

        # get all translations
        translations = getattr(self, self._meta.translations_accessor).all()

        # if no translation exists, bail out now
        if len(translations) == 0:
            return default

        # organize translations into a nice dict
        translation_dict = dict((t.language_code, t) for t in translations)

        # see if we have the right language, or any language in fallbacks
        for code in (get_language(),
                     settings.LANGUAGE_CODE) + FALLBACK_LANGUAGES:
            try:
                translation = translation_dict[code]
            except KeyError:
                continue
            break
        else:
            # none of the fallbacks was found, pick an arbitrary translation
            translation = translation_dict.popitem()[1]

        setattr(self, self._meta.translations_cache, translation)
        return getattr(translation, name, default)

    def get_available_languages(self):
        """ Get a list of all available language_code in db. """
        qs = getattr(self, self._meta.translations_accessor).all()
        if qs._result_cache is not None:
            return [obj.language_code for obj in qs]
        return qs.values_list('language_code', flat=True)

    #===========================================================================
    # Internals
    #===========================================================================

    @property
    def _shared_field_names(self):
        if getattr(self, '_shared_field_names_cache', None) is None:
            opts = self._meta
            self._shared_field_names_cache = opts.get_all_field_names()
            for name in tuple(self._shared_field_names_cache):
                try:
                    attname = opts.get_field(name).get_attname()
                except FieldDoesNotExist:
                    pass
                else:
                    if attname and attname != name:
                        self._shared_field_names_cache.append(attname)
        return self._shared_field_names_cache

    @property
    def _translated_field_names(self):
        if getattr(self, '_translated_field_names_cache', None) is None:
            opts = self._meta.translations_model._meta
            self._translated_field_names_cache = opts.get_all_field_names()
            for name in tuple(self._translated_field_names_cache):
                try:
                    attname = opts.get_field(name).get_attname()
                except FieldDoesNotExist:
                    pass
                else:
                    if attname and attname != name:
                        self._translated_field_names_cache.append(attname)
        return self._translated_field_names_cache