示例#1
0
class RelatedAnnouncement(models.Model):
    """
    Abstract model to link another archive item (e.g. visuals) to an Announcement. The Model should be
    subclassed and used as a many-to-many intermediary model::

        class RelatedAnnouncementImage( RelatedRAnnouncement ):
            archive_item = models.ForeignKey( Image, verbose_name=('Image') )

            class Meta:
                verbose_name = _('...')
    """

    announcement = TranslationForeignKey(
        Announcement, verbose_name=_('Related announcement'))
    # The announcement to link with another archive item.

    order = models.PositiveSmallIntegerField(blank=True, null=True)
    # Used to define an order for the archive items, in case this should not be via the alphabetic order of the id.

    main_visual = models.BooleanField(default=False)
    # Defines the primary visual for an announcement - the user is responsible for only selecting one main visual.

    override_id = models.SlugField(blank=True,
                                   null=True,
                                   verbose_name=_('Override ID'))
    # In case you ingest a visual into several announcements, this field can be
    # used to override the id.

    hide = models.BooleanField(default=False, verbose_name=_('Hide on kiosk'))

    # Define if the visual should be hidden if used for e.g. the kiosk

    def __unicode__(self):
        return ugettext("Archive Item for Announcement %s" %
                        (unicode(self.announcement.id)))

    class Meta:
        abstract = True
示例#2
0
class ReleaseImageComparison(RelatedRelease):
    """ Stock Images related to a release. """
    archive_item = TranslationForeignKey(
        ImageComparison, verbose_name=_('Related Image Comparison'))
示例#3
0
class ReleaseStockImage(RelatedRelease):
    """ Stock Images related to a release. """
    archive_item = TranslationForeignKey(Image,
                                         verbose_name=_('Related Stock Image'))
示例#4
0
class ReleaseVideo(RelatedRelease):
    """ Images related to a release. """
    archive_item = TranslationForeignKey(Video,
                                         verbose_name=_('Related Video'))
示例#5
0
class ReleaseImage(RelatedRelease):
    """ Images related to a release. """
    archive_item = TranslationForeignKey(Image,
                                         verbose_name=_('Related Image'))
    zoomable = models.BooleanField(default=False,
                                   verbose_name=_('Zoomable if main'))
示例#6
0
class Release(ArchiveModel, TranslationModel):
    #
    # Field definitions
    #
    id = models.SlugField(
        primary_key=True,
        help_text=_(u'Id of release - e.g. heic0801. The id must be unique.'))

    old_ids = models.CharField(
        verbose_name=_("Old Ids"),
        max_length=50,
        blank=True,
        help_text=_(
            u'For backwards compatibility: Historic ids of this press release.'
        ))

    # Type of the release - see ReleaseType model for more information.
    release_type = TranslationForeignKey(ReleaseType,
                                         blank=False,
                                         null=False,
                                         default=1)

    # Title of of the press release
    title = models.CharField(
        max_length=255,
        db_index=True,
        help_text=
        _(u"Title is shown in browser window. Use a good informative title, since search engines normally display the title on their result pages."
          ))

    # Subtitle of of the press release
    subtitle = models.CharField(
        max_length=255,
        blank=True,
        help_text=_(u"Optional subtitle to be shown just above the headline."))

    release_city = models.CharField(
        max_length=100,
        blank=True,
        help_text=_(
            u"The city of the release - e.g. Paris. Can be left blank."))

    headline = models.TextField(
        blank=True,
        help_text=
        _(u'HTML code in lead is not allowed. The lead is further more normally shown in search engine results, making the description an effective way of capturing users attention.'
          ))

    description = models.TextField(blank=True)

    notes = models.TextField(blank=True)

    more_information = models.TextField(blank=True)

    links = models.TextField(blank=True, help_text=_(u'Help text'))

    subject_category = metadatafields_trans.TranslationAVMSubjectCategoryField(
    )

    subject_name = metadatafields_trans.TranslationAVMSubjectNameField()

    facility = metadatafields_trans.TranslationFacilityManyToManyField()

    instruments = metadatafields_trans.TranslationInstrumentManyToManyField()

    publications = metadatafields_trans.TranslationAVMPublicationField()

    # Field for posting disclaimers on a press release - e.g. if it's retracted.
    disclaimer = models.TextField(
        blank=True,
        help_text=
        _(u'Disclaimer for press release - usually e.g. retractions of previously issued press releases.'
          ))

    related_images = TranslationManyToManyField(Image,
                                                through='ReleaseImage',
                                                only_sources=True)

    related_videos = TranslationManyToManyField(Video,
                                                through='ReleaseVideo',
                                                only_sources=True)

    related_comparisons = TranslationManyToManyField(
        ImageComparison, through='ReleaseImageComparison', only_sources=True)

    stock_images = TranslationManyToManyField(Image,
                                              only_sources=True,
                                              through='ReleaseStockImage',
                                              related_name='stock')

    meltwater_keywords = models.TextField(
        blank=True,
        help_text=_(u'Used to store Meltwater Keywords.'),
        verbose_name=_(u'Keywords'))

    #
    # Kids press releases
    #
    kids_title = models.CharField(max_length=255, blank=True)

    kids_description = models.TextField(blank=True)

    kids_image = models.ForeignKey(
        Image,
        blank=True,
        null=True,
        related_name='kids_image_release_set',
        help_text=_(u'Use this to override the default Release image.'))

    def get_embargo_login(self):
        return settings.ARCHIVE_EMBARGO_LOGIN

    def _set_main_image(self, im):
        self._main_image_cache = im

    def _get_main_image(self):
        try:
            return self._main_image_cache
        except AttributeError:
            for visual in self.releaseimage_set.all():
                if visual.main_visual:
                    self._main_visual_cache = visual.archive_item
                    return visual.archive_item

    main_image = property(_get_main_image)

    def _set_main_video(self, vid):
        self._main_video_cache = vid

    def _get_main_video(self):
        try:
            return self._main_video_cache
        except AttributeError:
            try:
                vid = ReleaseVideo.objects.filter(
                    main_visual=True,
                    release=self if self.is_source() else self.source).get()
                self._main_video_cache = vid.archive_item
                return vid.archive_item
            except (IndexError, ObjectDoesNotExist, MultipleObjectsReturned):
                return None

    main_video = property(_get_main_video)

    @staticmethod
    def store_main_visuals(object_list):
        """
        Fetches the main image and videos for all releases in object list using one query, instead
        of one query per release.
        """
        relids = [x.id if x.is_source() else x.source_id for x in object_list]

        ims = ReleaseImage.objects.filter(
            main_visual=True,
            release__in=relids).select_related('archive_item')
        vids = ReleaseVideo.objects.filter(
            main_visual=True,
            release__in=relids).select_related('archive_item')

        release_mapping = {}
        for im in ims:
            release_mapping[im.release_id] = im.archive_item

        for obj in object_list:
            try:
                im = release_mapping[obj.id if obj.is_source() else obj.
                                     source_id]
                obj._set_main_image(im)
            except KeyError:
                obj._set_main_image(None)

        release_mapping = {}
        for vid in vids:
            release_mapping[vid.release] = vid.archive_item

        for obj in object_list:
            try:
                vid = release_mapping[obj.id if obj.is_source() else obj.
                                      source_id]
                obj._set_main_video(vid)
            except KeyError:
                obj._set_main_video(None)

    def rename(self, new_pk):
        '''
        Extend Archive's rename() to send email notification if original is renamed
        '''
        if self.published and self.is_source() and hasattr(
                settings, 'RELEASE_RENAME_NOTIFY'):
            msg_subject = 'Press Release renamed: %s -> %s' % (self.pk, new_pk)
            msg_body = """https://www.eso.org/public/news/%s/""" % new_pk
            msg_from = getattr(settings, 'DEFAULT_FROM_EMAIL', '')
            msg_to = getattr(settings, 'RELEASE_RENAME_NOTIFY', '')
            if msg_from and msg_to:
                send_mail(msg_subject,
                          msg_body,
                          msg_from,
                          msg_to,
                          fail_silently=False)

        return super(Release, self).rename(new_pk)

    def is_published(self):
        return datetime.now() >= self.release_date

    @staticmethod
    def get_latest_release(count=1):
        qs = Release.objects.filter(
            release_date__lte=datetime.now(),
            published=True).order_by('-release_date')[:count]
        return qs

    class Meta:
        ordering = ['-release_date', '-id']
        get_latest_by = "release_date"
        verbose_name = _('Press Release')
        verbose_name_plural = _('Press Releases')
        permissions = [
            ("view_only_non_default", "Can view only non default language"),
        ]

    def get_absolute_url(self):
        return translation_reverse(
            'releases_detail',
            args=[str(self.id if self.is_source() else self.source.id)],
            lang=self.lang)

    def __unicode__(self):
        return self.id

    class Translation:
        fields = [
            'description', 'disclaimer', 'headline', 'links',
            'more_information', 'notes', 'release_city', 'subtitle', 'title',
            'kids_title', 'kids_description'
        ]
        excludes = [
            'published', 'last_modified', 'created', 'meltwater_keywords'
        ]
        set_deep_copy = ['releasetranslationcontact_set']

    class Archive:
        doc = ResourceManager(type=types.DocType)
        pdf = ResourceManager(type=types.PDFType)
        text = ResourceManager(type=types.TxtType)
        sciencepapers = ResourceManager(type=types.DirType,
                                        verbose_name=_('Science Papers'))

        class Meta:
            root = settings.RELEASE_ARCHIVE_ROOT
            release_date = True
            embargo_date = True
            last_modified = True
            created = True
            published = True
            rename_pk = ('releases_release', 'id')
            rename_fks = (
                ('releases_release', 'source_id'),
                ('releases_release_facility', 'release_id'),
                ('releases_release_instruments', 'release_id'),
                ('releases_release_subject_category', 'release_id'),
                ('releases_release_subject_name', 'release_id'),
                ('releases_releasecontact', 'release_id'),
                ('releases_releaseimage', 'release_id'),
                ('releases_releasevideo', 'release_id'),
                ('releases_releasetranslation', 'release_id'),
                ('media_imagecomparison', 'release_date_owner'),
                ('media_image', 'release_date_owner'),
                ('media_video', 'release_date_owner'),
            )
            sort_fields = ['last_modified', 'release_date']
            clean_html_fields = [
                'description', 'notes', 'more_information', 'links',
                'disclaimer'
            ]
示例#7
0
class ImageComparison( ArchiveModel, TranslationModel ):
    """
    Image comparisons allows to compare two images with a nice
    JavaScript feature that will reveal the two images when hovering
    the mouse over the image.
    """
    id = archive_fields.IdField()
    priority = archive_fields.PriorityField( help_text=_( u'Assessment of the quality of the image comparison (100 highest, 0 lowest). Higher priority images are ranked higher in search results than lower priority images.' ) )

    featured = models.BooleanField(default=True)

    title = metadatafields.AVMTitleField()
    description = metadatafields.AVMDescriptionField()
    credit = metadatafields.AVMCreditField( default=DEFAULT_CREDIT_FUNC )

    image_before = TranslationForeignKey( Image, related_name='imagecomparison_before_set', null=True )
    image_after = TranslationForeignKey( Image, related_name='imagecomparison_after_set', null=True )

    def __init__( self, *args, **kwargs ):
        super( ImageComparison, self ).__init__( *args, **kwargs )
        if not self.credit:
            self.credit = DEFAULT_CREDIT

    def __unicode__( self ):
        return self.title

    def height_ratio( self ):
        return float( self.image_before.height ) / float( self.image_before.width )

    def height_1280( self ):
        return int( 1280 * self.height_ratio() )

    def get_absolute_url(self):
        return translation_reverse( 'imagecomparisons_detail', args=[str( self.id if self.is_source() else self.source.id )], lang=self.lang )

    class Meta:
        ordering = ( '-priority', '-release_date', )
        app_label = 'media'
        permissions = [
            ( "view_only_non_default", "Can view only non default language" ),
        ]

    class Translation:
        fields = [ 'title', 'description', 'credit', ]
        excludes = [ 'published', 'last_modified', 'created', ]

    class Archive:
        original = ImageResourceManager( type=types.OriginalImageType )
        large = ImageResourceManager( derived='original', type=types.LargeJpegType )
        screen = ImageResourceManager( derived='original', type=types.ScreensizeJpegType )
        news = ImageResourceManager( derived='original', type=types.NewsJpegType )
        newsmini = ImageResourceManager( derived='original', type=types.NewsMiniJpegType )
        newsfeature = ImageResourceManager( derived='original', type=types.NewsFeatureType )
        medium = ImageResourceManager( derived='original', type=types.MediumJpegType )
        wallpaperthumbs = ImageResourceManager( derived='original', type=types.WallpaperThumbnailType )
        potwmedium = ImageResourceManager( derived='original', type=types.POTWMediumThumbnailJpegType )
        thumbs = ImageResourceManager( derived='original', type=types.ThumbnailJpegType )
        thumb350x = ImageResourceManager( derived='original', type=types.Thumb350xType )
        screen640 = ImageResourceManager(derived='original', type=types.Screen640Type)
        thumb300y = ImageResourceManager(derived='original', type=types.Thumb300yType)
        thumb350x = ImageResourceManager(derived='original', type=types.Thumb350xType)
        thumb700x = ImageResourceManager(derived='original', type=types.Thumb700xType)

        class Meta:
            root = getattr( settings, 'IMAGECOMPARISON_ARCHIVE_ROOT', '' )
            release_date = True
            embargo_date = True
            release_date_owner = True
            last_modified = True
            created = True
            published = True
            rename_pk = ( 'media_imagecomparison', 'id' )
            rename_fks = (
                ( 'media_imagecomparison', 'source_id' ),
                ( 'media_image', 'release_date_owner' ),
            )
            sort_fields = ['last_modified', 'release_date', ]
            clean_html_fields = ['description', 'credit']
示例#8
0
class TranslationModel(models.Model):
    """
    Base class for models that needs multilingual capabilities. It adds two
    fields needed internally, a model manager to properly work with
    the data in the model and a custom save method.

    Note, you must ensure that the save method is called even in cases
    where your model overrides the save method or inherit a save method
    from another base class. The save method will be default call its
    base class save method, but this can be prevented as well in such cases.

    The base class also requires you to specify which fields in your model
    are to be translated, which are to be inherited and which are not to be
    translated nor inherited.

    Example::

    class SomeModel( SomeMoreImportantModel, TranslationModel ):
        ...
        title = ...
        template = ...
        release_date = ...
        ...

        class Translation:
            fields = ['title',]
            excludes = ['template',]

    Above example specifies that title is to be translated, template not, and
    that release_date is inherited from the source.
    """

    #
    # Fields
    #
    lang = LanguageField(verbose_name=_('Language'),
                         max_length=7,
                         default=_get_defaut_lang,
                         db_index=True)
    source = TranslationForeignKey('self',
                                   verbose_name=_('Translation source'),
                                   related_name='translations',
                                   null=True,
                                   blank=True,
                                   only_sources=False,
                                   limit_choices_to={'source__isnull': True})
    translation_ready = models.BooleanField(
        default=False,
        help_text=
        _('When you check this box and save this translation, an automatic notification email will be sent.'
          ))

    #
    # Manager
    #

    # Object manager to all source objects
    #
    # Note, the default manager *must* be a TranslationSourceManager
    # or a subclass thereof which limits choices to *only* source
    # objects. The reason for this is that forward/reverse relationship
    # managers uses the default manager as base class - thus this ensures
    # that you can only make relations to source objects, which is normally
    # what you want (i.e. the ForeignKey/ManyToManyField is an inherited
    # relation).
    objects = TranslationSourceManager()

    translation_objects = TranslationOnlyManager()

    #
    # Methods
    #
    def save(self, propagate=True, **kwargs):
        """
        Save method that ensures the translations are in sync with their source.

        Note, if `propagate=False` the super class' save method is not
        called. It just does the update of the translations. This is useful
        if you Model class is inheriting from several classes and you want to
        call this save method, but don't want it to propagate.

        Note, if you source object has not been saved, it will automatically
        be saved first, since all translations *must* have a source.
        """
        # Language must be set - using default language if
        # nothing has been set.
        if not self.lang:
            self.lang = settings.LANGUAGE_CODE

        # Propagate inherited properties and foreign keys
        # from source to translations.
        source = self.source

        if settings.USE_I18N:
            if source:
                # Save source if it hasn't already been saved.
                if not source.pk:
                    source.save()
                    self.source = source

                # Translation object
                self._update_from_source()
            else:
                # Source
                self._update_translations()

        if propagate:
            super(TranslationModel, self).save(**kwargs)

        # Source objects get the cache cleared in the post_save, but
        # for translations we do it here:
        if source and hasattr(self, 'clear_cache'):
            self.clear_cache()

    def _excludes(self, exclude=None):
        """
        Return a list of all fields local to the model that is
        not to be inherited from the source object.
        """
        if exclude is None:
            exclude = []
        return [
            'source', 'lang', 'translation_ready',
            str(self._meta.pk.name)
        ] + self.Translation.fields + self.Translation.excludes + exclude

    def _update_translations(self):
        """
        Update inherited properties and foreign keys in translations
        using the source. The update is done in one query and it
        doesn't hit the save method of the translations.
        """
        # Only sources are allowed to call this method.
        # and, we must be updating the source, not adding it.
        try:
            if not self.source and self.pk:  # TODO: Tricky - actually self.pk might not be none for a new object
                # Get all fields to exclude and setup list of values to update.
                values = {}
                all_excludes = self._excludes()

                # Build list inherited properties and foreign keys to save.
                for field in self._meta.local_fields:
                    if field.name not in all_excludes:
                        try:
                            values[field.name] = getattr(self, field.name)
                        except ObjectDoesNotExist:
                            pass

                # Update all translations if there is anything to update.
                if values:
                    self.translations.all().update(**values)
        except ObjectDoesNotExist:
            pass

    def _update_from_source(self):
        """
        Copy all inherited properties from the source
        ( old data will be overwritten ).
        """
        if self.source:
            # Get all fields to excludes.
            all_excludes = self._excludes()

            # Get value from source.
            for field in self._meta.fields:
                if field.name not in all_excludes:
                    setattr(self, field.name, getattr(self.source, field.name))

    def is_translation(self):
        """
        Determine if this instance is a translation or source object.
        """
        return self.source_id is not None

    def is_source(self):
        """
        Determine if this instance is a translation or source object.
        """
        return self.source_id is None

    def get_source(self):
        """
        Returns the source object, regardless of whether this instance is a translation or a source object.
        """
        return self if self.is_source() else self.source

    def validate_unique(self, exclude=None):
        """
        Validate that translation language is *not* identical to source language,
        and that the source id is not matching the a language id pattern (<id>-<code>).
        """
        # Validate if translation language equals source language
        if self.is_translation() and self.source.lang == self.lang:
            raise ValidationError({
                'lang': [
                    "Translation language must not be identical to translation source language."
                ]
            })

        # Validate source id prefix matches the language
        if self.is_source() and self.pk:
            try:
                m = LANG_SLUG_RE.match(self.pk)
                if m and self.lang != m.group(1):
                    raise ValidationError(
                        "Language doesn't match ID prefix '-%s'." % m.group(1))
            except TypeError:
                pass  # In case of non char id's, just ignore this error.

        super(TranslationModel, self).validate_unique(exclude=exclude)

    def get_missing_translations(self, *args, **kwargs):
        """
        Get a list of missing languages
        """
        data = self.get_translations(*args, **kwargs)

        def missing_filter(x):
            try:
                if (hasattr(settings, 'LANGUAGE_DISABLE_TRANSLATIONS')
                        and x[0] in settings.LANGUAGE_DISABLE_TRANSLATIONS
                    ) or x[0] == settings.LANGUAGE_CODE:
                    return False
                return not data['translations'][x[0]].translation_ready
            except KeyError:
                return True

        return filter(missing_filter, settings.LANGUAGES)

    def get_translations(self, filter_kwargs=None, include_self=False):
        """
        Get a dictionary of translations organised by:

        1) Countries: List of countries - each element in the list represents a country, and
            is a list of languages in that country (i.e. its a lists of lists of dictionaries)::

                [ <country>, ... ] where <country> is a list like [ <language >, ... ] where
                language is a dictionary like with the following keys: object, country, name, lang

        2) Main languages:
        """
        if filter_kwargs is None:
            filter_kwargs = {'published': True, 'translation_ready': True}

        data = {
            'languages': [],
            'countries': [],
            'translations': {},
        }

        def _ctx_for_translation(t, lang, ctry, name, code):
            return {
                'object': t,
                'country': ctry,
                'name': name,
                'lang': lang,
                'full_code': code,
            }

        # Retrieve all translations for this object (including the source language)
        if self.is_translation():
            translations = list(
                self.source.translations.filter(
                    **filter_kwargs)) + [self.source]
        else:
            translations = list(
                self.translations.filter(**filter_kwargs)) + [self]

        # Make a search dictionary (include source language, and exclude it's own language).
        searchmap = dict([(t.lang, t) for t in translations
                          if include_self or t.lang != self.lang])
        data['translations'] = searchmap

        # Make list of main languages
        for lang, ctry, name, code in MAIN_LANGUAGES_LIST:
            if code in searchmap:
                data['languages'].append(
                    _ctx_for_translation(searchmap[code], lang, ctry, name,
                                         code))

        # Make list of countries
        for ctry, langs in COUNTRIES_LIST:
            tmp = []
            for lang, name, code in langs:
                if code in searchmap:
                    # Mon Aug 12 10:56:07 CEST 2013 - Mathias Andre
                    # We auto-generate english translations for e.g. USA, IRELAND,
                    # but we don't want these linked on the websites, so we skip them
                    # from the translation list
                    if code.startswith('en') and code != 'en':
                        continue
                    tmp.append(
                        _ctx_for_translation(searchmap[code], lang, ctry, name,
                                             code))

            if tmp:
                data['countries'].append(tmp)

        return data

    def generate_duplicate_id(self, lang):
        """
        Returns a new id including the given language code
        """
        if self.source:
            return '%s-%s' % (self.source.pk, lang)
        else:
            return '%s-%s' % (self.pk, lang)

    @classmethod
    def send_notification_mail(cls, sender, instance, raw=False, **kwargs):
        """
        Send a notification email to a specified address once a translation is ready.
        """
        if raw:
            return
        if instance.translation_ready:
            from_email = settings.DEFAULT_FROM_EMAIL

            #  For http://www.djangoplicity.org/bugs/view.php?id=19639
            #  We only send notifications for 'main' languages if they
            #  are defined in the settings:
            notification_langs = getattr(settings,
                                         'LANGUAGE_ENABLE_NOTIFICATIONS', [])
            if not notification_langs:
                return

            # Check whether the 'translation ready' flag has just been set:
            try:
                old = sender.objects.get(pk=instance.pk)
                if old.translation_ready:
                    return
            except sender.DoesNotExist:
                pass

            # Disable signals to prevent loops
            models.signals.pre_save.disconnect(cls.send_notification_mail,
                                               sender=cls)

            for lang in notification_langs:
                if not instance.lang.startswith(lang):
                    continue
                for code, email in settings.LANGUAGES_CONTACTS.iteritems():
                    if code.startswith(lang) and code != instance.lang:

                        # Check if a translation already exists:
                        try:
                            cls.objects.get(source=instance.source, lang=code)
                            # If there is a translation we continue
                            continue
                        except cls.DoesNotExist:
                            kwargs = {
                                'pk': instance.generate_duplicate_id(code),
                                'source': instance.source,
                                'lang': code,
                                'created': datetime.today(),
                                'translation_ready': True,
                                'published': instance.published,
                            }
                            translation = cls(**kwargs)
                            copy_family_translation_fields(
                                instance, translation)
                            translation.save()

                            if not email:
                                continue

                            try:
                                from django.contrib.sites.models import Site
                                domain = Site.objects.get_current().domain
                            except ImportError:
                                domain = ''

                            subject = "%s translation %s ready" % (
                                unicode(translation._meta.verbose_name),
                                translation.pk)
                            html_body = render_to_string(
                                'archives/ready_email.html', {
                                    'type': translation._meta.verbose_name,
                                    'source': instance,
                                    'translation': translation,
                                    'site_url': domain
                                })
                            text_body = strip_tags(html_body)

                            # Send
                            if subject and html_body and from_email:
                                try:
                                    msg = EmailMultiAlternatives(
                                        subject, text_body, from_email,
                                        [email])
                                    msg.attach_alternative(
                                        html_body, 'text/html')
                                    msg.send()
                                except Exception:
                                    # Just ignore error if SMTP server is down.
                                    pass

            # Re-enable signals
            models.signals.pre_save.connect(cls.send_notification_mail,
                                            sender=cls)

    def update_family_translations(self, **kwargs):
        '''
        Generate/update translations of similar languages
        e.g.: de-ch -> de, de-at
        '''
        if not settings.USE_I18N:
            return

        main_language = self.lang
        if '-' in main_language:
            main_language = main_language.split('-')[0]

        # Get list of languages in the same family
        languages = []
        for code, dummy_name in settings.LANGUAGES:
            if code.startswith(main_language) and code != self.lang:
                languages.append(code)

        # Search for corresponding Proxy class
        if self.is_source():
            module = inspect.getmodule(self)
            proxymodel_name = '%sProxy' % self.__class__.__name__

            if not hasattr(module, proxymodel_name):
                return
            proxymodel = getattr(module, proxymodel_name)
            source = self
        else:
            proxymodel = self.__class__
            source = self.source

        # Disable signals to prevent loops
        models.signals.pre_save.disconnect(
            self.__class__.send_notification_mail, sender=self)

        for lang in languages:
            try:
                translation = proxymodel.objects.get(source=self.pk, lang=lang)
            except ObjectDoesNotExist:
                kwargs = {
                    'pk': self.generate_duplicate_id(lang),
                    'source': source,
                    'lang': lang,
                    'created': datetime.today(),
                    'translation_ready': True,
                    'published': self.published,
                }
                translation = proxymodel(**kwargs)

            copy_family_translation_fields(self, translation)
            translation.published = self.published  # Carry over 'published' status
            translation.save()

        # Re-enable signals
        models.signals.pre_save.connect(self.__class__.send_notification_mail,
                                        sender=self)

    class Translation:
        fields = []  # Translated fields - should be overwritten in subclass.
        excludes = [
        ]  # Non-inherited fields - should be overwritten in subclass.
        set_deep_copy = [
        ]  # 1-to-N relationships that will be cloned for same-family languages

        # If set to False we don't show the original version if there is no
        # translation and the original is in a different language than the site
        # default (e.g.: Don't show 'es-cl' announcements on 'en' announcements list
        non_default_languages_in_fallback = True

    class Meta:
        abstract = True
        unique_together = ('lang', 'source')
示例#9
0
class AnnouncementImageComparison(RelatedAnnouncement):
    '''
    Image Comparisons related to an announcement
    '''
    archive_item = TranslationForeignKey(
        ImageComparison, verbose_name=_('Related Image Comparison'))
示例#10
0
class AnnouncementVideo(RelatedAnnouncement):
    """ Videos related to an announcement. """
    archive_item = TranslationForeignKey(Video,
                                         verbose_name=_('Related Video'))
示例#11
0
class AnnouncementImage(RelatedAnnouncement):
    """ Images related to an announcement. """
    archive_item = TranslationForeignKey(Image,
                                         verbose_name=_('Related Image'))
示例#12
0
class Announcement(ArchiveModel, TranslationModel):
    """
    Similar to press releases but with fewer fields.
    """
    id = archive_fields.IdField()
    title = archive_fields.TitleField()
    subtitle = models.CharField(
        max_length=255,
        blank=True,
        help_text=_(u"Optional subtitle to be shown just above the headline."))
    description = archive_fields.DescriptionField()
    contacts = models.TextField(blank=True, help_text=_(u'Contacts'))
    links = models.TextField(blank=True, help_text=_(u'Links'))
    featured = models.BooleanField(default=True)
    related_images = TranslationManyToManyField(Image,
                                                through='AnnouncementImage')
    related_videos = TranslationManyToManyField(Video,
                                                through='AnnouncementVideo')
    related_comparisons = TranslationManyToManyField(
        ImageComparison,
        through='AnnouncementImageComparison',
        only_sources=True)

    announcement_type = TranslationForeignKey(AnnouncementType,
                                              blank=True,
                                              null=True,
                                              default=None)

    def get_embargo_login(self):
        return settings.ARCHIVE_EMBARGO_LOGIN

    def _set_main_visual(self, vis):
        self._main_visual_cache = vis

    def _get_main_visual(self):
        try:
            return self._main_visual_cache
        except AttributeError:
            pass

        for visual in self.announcementimage_set.all():
            if visual.main_visual:
                self._main_visual_cache = visual.archive_item
                return visual.archive_item

        for visual in self.announcementimagecomparison_set.all():
            if visual.main_visual:
                self._main_visual_cache = visual.archive_item
                return visual.archive_item

        for visual in self.announcementvideo_set.all():
            if visual.main_visual:
                self._main_visual_cache = visual.archive_item
                return visual.archive_item

        return None

    main_visual = property(_get_main_visual)

    def rename(self, new_pk):
        '''
        Extend Archive's rename() to send email notification if original is renamed
        '''
        if self.published and self.is_source() and hasattr(
                settings, 'ANNOUNCEMENT_RENAME_NOTIFY'):
            msg_subject = 'Announcement renamed: %s -> %s' % (self.pk, new_pk)
            msg_body = """https://www.eso.org/public/announcements/%s/""" % new_pk
            msg_from = getattr(settings, 'DEFAULT_FROM_EMAIL', '')
            msg_to = getattr(settings, 'ANNOUNCEMENT_RENAME_NOTIFY', '')
            if msg_from and msg_to:
                send_mail(msg_subject,
                          msg_body,
                          msg_from,
                          msg_to,
                          fail_silently=False)

        return super(Announcement, self).rename(new_pk)

    @staticmethod
    def store_main_visuals(object_list):
        '''
        Fetches the main image for all announcements in object list using one query, instead
        of one query per announcement.
        '''
        annids = [x.id if x.is_source() else x.source_id for x in object_list]

        images = (AnnouncementImage.objects.filter(
            main_visual=True,
            announcement__in=annids).select_related('archive_item'))
        videos = (AnnouncementVideo.objects.filter(
            main_visual=True,
            announcement__in=annids).select_related('archive_item'))
        comparisons = (AnnouncementImageComparison.objects.filter(
            main_visual=True,
            announcement__in=annids).select_related('archive_item'))

        announcement_mapping = {}
        for video in videos:
            announcement_mapping[video.announcement_id] = video.archive_item
        for image in images:
            announcement_mapping[image.announcement_id] = image.archive_item
        for comparison in comparisons:
            announcement_mapping[
                comparison.announcement_id] = comparison.archive_item

        for obj in object_list:
            try:
                visual = announcement_mapping[obj.id if obj.is_source(
                ) else obj.source_id]
                obj._set_main_visual(visual)
            except KeyError:
                obj._set_main_visual(None)

    @staticmethod
    def get_latest_announcement(len=1, only_featured=False):
        qs = Announcement.objects.filter(release_date__lte=datetime.now(),
                                         published=True)
        if only_featured:
            qs = qs.filter(featured=True)
        return qs.order_by('-release_date')[:len]

    class Meta:
        ordering = ['-release_date', '-id']
        get_latest_by = "release_date"
        permissions = [
            ("view_only_non_default", "Can view only non default language"),
        ]

    def get_absolute_url(self):
        return translation_reverse(
            'announcements_detail',
            args=[str(self.id if self.is_source() else self.source.id)],
            lang=self.lang)

    def __unicode__(self):
        return u"%s: %s" % (self.id, self.title)

    class Translation:
        fields = ['title', 'subtitle', 'description', 'contacts', 'links']
        excludes = [
            'published',
            'last_modified',
            'created',
        ]
        non_default_languages_in_fallback = False  # Don't show non-en anns. if no en translation is available

    class Archive:
        pdf = ResourceManager(type=types.PDFType)
        pdfsm = ResourceManager(type=types.PDFType)
        sciencepaper = ResourceManager(type=types.PDFType,
                                       verbose_name=_('Science paper'))

        class Meta:
            root = ANNOUNCEMENTS_ROOT
            release_date = True
            embargo_date = True
            last_modified = True
            created = True
            published = True
            rename_pk = ('announcements_announcement', 'id')
            rename_fks = (
                ('announcements_announcement', 'source_id'),
                ('announcements_announcementimage', 'announcement_id'),
                ('announcements_announcementvideo', 'announcement_id'),
                ('media_image', 'release_date_owner'),
                ('media_video', 'release_date_owner'),
                ('media_imagecomparison', 'release_date_owner'),
            )
            sort_fields = ['last_modified', 'release_date']
            clean_html_fields = ['description', 'links', 'contacts']
示例#13
0
class VideoSubtitle( ArchiveModel, models.Model ):
    """
    Model for storing subtitles for videos.
    """
    id = archive_fields.IdField()
    video = TranslationForeignKey( Video )
    lang = models.CharField( verbose_name=_( 'Language' ), max_length=7, choices=SUBTITLE_LANGUAGES, default=settings.LANGUAGE_CODE, db_index=True )

    def save( self, **kwargs ):
        """
        Override save() to always set id as video_id + lang

        # TODO: check video renaming
        """
        old_id = self.id
        self.id = self.video.id + self.lang

        if old_id != self.id:  # delete old
            try:
                old = VideoSubtitle.objects.get( id=old_id )
                old.delete()
            except VideoSubtitle.DoesNotExist:
                pass

        # In some cases where 'created' was not set upon creation the save
        # will fail so we manually set it
        if not self.created:  # pylint: disable=E0203
            self.created = datetime.datetime.today()

        super( VideoSubtitle, self ).save( **kwargs )

        transaction.on_commit(
            lambda: update_youtube_caption.delay(self.pk)
        )

    def __unicode__( self ):
        return dict( SUBTITLE_LANGUAGES ).get( self.lang, self.lang )

    def update_youtube_caption(self, logger=None):
        '''
        Upload the given subtitle to YouTube
        '''
        if logger is None:
            logger = logging.getLogger(__name__)

        if not youtube_configured or not self.video or not self.video.youtube_video_id:
            logger.warning('No video or YouTube ID for subtitle "%s"', self.pk)
            return

        response = youtube_videos_list(
            id=self.video.youtube_video_id,
            part='snippet'
        )

        if not response['items']:
            raise YouTubeUnknownID('Unkown YouTube ID: "%s"' %
                    self.youtube_video_id)

        snippet = dict(
            videoId=self.video.youtube_video_id,
            language=self.lang,
            name='',
            isDraft=False
        )

        # Check if caption already exists
        response = youtube_captions_list(
            part='snippet',
            video_id=self.video.youtube_video_id
        )

        # We use wait_for_resource in case the NFS is slow to synchronise
        # between the different servers
        srt = wait_for_resource(self, 'srt')

        for item in response['items']:
            if item['snippet']['language'] == self.lang:
                # Subtitle already exist, we update it
                youtube_captions_update(
                    part='snippet',
                    body=dict(
                        id=item['id'],
                        snippet=snippet
                    ),
                    media_body=srt.file.name
                )
                break
        else:
            # New subtitle, we insert it
            youtube_captions_insert(
                part='snippet',
                body=dict(
                    snippet=snippet
                ),
                media_body=srt.file.name
            )

    class Meta:
        verbose_name = _(u'Video Subtitle')
        app_label = 'media'

    class Archive:
        srt = ResourceManager( type=types.SubtitleType, verbose_name=_( u"Subtitle (srt)" ) )

        class Meta:
            root = settings.VIDEOS_ARCHIVE_ROOT
            release_date = False
            embargo_date = False
            last_modified = True
            created = True
            published = True