コード例 #1
0
def palanaeum_context(request):
    """
    Put all useful information into context, like page title.
    """
    if hasattr(request, 'user') and request.user.is_staff:
        suggestions = Entry.objects.filter(versions__is_approved=False).distinct().count()
        suggestions += AudioSource.objects.filter(is_approved=False).count()
        suggestions += ImageSource.objects.filter(is_approved=False).count()
        is_staff = True
    else:
        suggestions = 0
        is_staff = False

    logo_path = configuration.get_config('logo_file') or static('palanaeum/img/palanaeum_logo.svg')
    palanaeum_app = apps.get_app_config('palanaeum')

    return {
        'PAGE_TITLE': configuration.get_config('page_title'),
        'TINYMCE_API_KEY': settings.TINYMCE_API_KEY,
        'GENERAL_SEARCH_PARAM_NAME': search.TextSearchFilter.GET_PARAM_NAME,
        'GOOGLE_ID': configuration.get_config('google_analytics'),
        'SUGGESTIONS_COUNT': suggestions,
        'STAFF': is_staff,
        'PALANAEUM_VERSION': settings.PALANAEUM_VERSION,
        'PALANAEUM_LOGO_URL': logo_path,
        'FAVICON16': favicon(16),
        'FAVICON32': favicon(32),
        'FAVICON96': favicon(96),
        'FAVICON120': favicon(120),
        'FAVICON152': favicon(152),
        'FAVICON167': favicon(167),
        'FAVICON180': favicon(180),
        'VERSION_TAG': palanaeum_app.version,
    }
コード例 #2
0
def view_event(request, event_id):
    """
    Display single Event page.
    """
    event = get_object_or_404(Event, pk=event_id)

    if not event.visible():
        raise PermissionDenied

    entry_ids = Entry.all_visible.filter(event=event).values_list('id', flat=True)
    entries_map = Entry.prefetch_entries(entry_ids, show_unapproved=is_contributor(request))

    entries = sorted(entries_map.values(), key=lambda e: e.order)
    sources = list(event.sources_iterator())

    approval_msg = get_config('approval_message')
    if event.review_state == Event.REVIEW_APPROVED:
        approval_explanation = get_config('review_reviewed_explanation')
    elif event.review_state == Event.REVIEW_PENDING:
        approval_explanation = get_config('review_pending_explanation')
    else:
        approval_explanation = ''

    return render(request, 'palanaeum/event.html', {'event': event, 'entries': entries, 'sources': sources,
                                                    'approval_msg': approval_msg,
                                                    'review_message': approval_explanation})
コード例 #3
0
ファイル: test_events.py プロジェクト: Palanaeum/palanaeum
    def test_event_page(self):
        self.event1.name = "Test event 1"
        self.event1.date = date(2018, 2, 3)
        self.event1.location = 'Warsaw'
        self.event1.tour = 'Test tour'
        self.event1.bookstore = 'Empik'
        self.event1.meta = 'Meta comment'
        self.event1.review_state = Event.REVIEW_APPROVED
        self.event1.save()

        response = self.client.get('/events/{}/'.format(self.event1.id), follow=True)
        body = response.content.decode()
        self.assertEqual(response.status_code, 200)
        self.assertInHTML('Warsaw', body)
        self.assertInHTML('Test tour', body)
        self.assertInHTML('Empik', body)
        self.assertNotIn('Meta comment', body)
        self.assertInHTML(get_config('review_reviewed_explanation'), body)

        self.event1.review_state = Event.REVIEW_PENDING
        self.event1.save()

        response = self.client.get('/events/{}/'.format(self.event1.id), follow=True)
        body = response.content.decode()
        self.assertEqual(response.status_code, 200)
        self.assertInHTML(get_config('review_pending_explanation'), body)

        self.event1.review_state = Event.REVIEW_NA
        self.event1.save()

        response = self.client.get('/events/{}/'.format(self.event1.id), follow=True)
        body = response.content.decode()
        self.assertEqual(response.status_code, 200)
コード例 #4
0
def upload_audio_page(request, event_id):
    """
    Display a page with Fine Uploader that will upload the AudioSources.
    """
    event = get_object_or_404(Event, pk=event_id)
    if request.user.is_staff:
        limit = get_config('audio_staff_size_limit')
    else:
        limit = get_config('audio_user_size_limit')
    readable_limit = "{:4.2f} MB".format(limit)
    return render(request, 'palanaeum/staff/upload_audio_page.html',
                  {'event': event, 'file_size_limit': limit * 1024 * 1024,
                   'readable_limit': readable_limit})
コード例 #5
0
def get_cloud_backend() -> CloudBackend:
    """
    Returns an instance of CloudBackend subclass configured and ready for action.
    If there is no cloud backend configured, None is returned.
    """
    backend_name = get_config('cloud_backend')
    if CLOUD_BACKENDS[backend_name] is None:
        return None
    package, class_name = CLOUD_BACKENDS[backend_name].rsplit('.', maxsplit=1)
    backend_class = getattr(importlib.import_module(package), class_name)
    assert (issubclass(backend_class, CloudBackend))
    cloud_login = get_config('cloud_login')
    cloud_passwd = get_config('cloud_passwd')
    return backend_class(cloud_login, cloud_passwd)
コード例 #6
0
def view_event(request, event_id):
    """
    Display single Event page.
    """
    event = get_object_or_404(Event, pk=event_id)

    if not event.visible():
        raise PermissionDenied

    # Caching only responses for anonymous users, as that's the majority of
    # visits and those don't change that much.
    anonymous_user_cache = "anon_event_view_{}".format(event_id)

    if request.user and request.user.is_anonymous:
        cached_response = cache.get(anonymous_user_cache)
        if cached_response is not None:
            cached_response, cache_timestamp = cached_response
            if cache_timestamp <= event.modified_date:
                return cached_response

    entry_ids = Entry.all_visible.filter(event=event).values_list('id',
                                                                  flat=True)
    entries_map = Entry.prefetch_entries(
        entry_ids, show_unapproved=is_contributor(request))

    entries = sorted(entries_map.values(), key=lambda e: e.order)
    sources = list(event.sources_iterator())

    approval_msg = get_config('approval_message')
    if event.review_state == Event.REVIEW_APPROVED:
        approval_explanation = get_config('review_reviewed_explanation')
    elif event.review_state == Event.REVIEW_PENDING:
        approval_explanation = get_config('review_pending_explanation')
    else:
        approval_explanation = ''

    response = render(
        request, 'palanaeum/event.html', {
            'event': event,
            'entries': entries,
            'sources': sources,
            'approval_msg': approval_msg,
            'review_message': approval_explanation
        })

    if request.user and request.user.is_anonymous:
        cache.set(anonymous_user_cache, (response, event.modified_date))

    return response
コード例 #7
0
def index(request):
    """
    Draw the home page.
    """
    page_length = UserSettings.get_page_length(request)
    newest_events = Event.all_visible.exclude(entries=None).prefetch_related('entries', 'tags')[:page_length]
    events_count = Event.all_visible.filter().count()
    entries_count = Entry.all_visible.filter().count()
    audio_sources_count = AudioSource.all_visible.filter().count()

    new_sources = []
    new_sources.extend(AudioSource.all_visible.order_by('-created_date')[:5])
    new_sources.extend(ImageSource.all_visible.order_by('-created_date')[:5])
    new_sources.sort(key=lambda source: source.created_date or
                                        timezone.datetime(1900, 1, 1, tzinfo=timezone.get_current_timezone()),
                     reverse=True)
    new_sources = new_sources[:5]

    related_sites = RelatedSite.objects.all()

    welcome_text = get_config('index_hello')

    return render(request, 'palanaeum/index.html', {'newest_events': newest_events, 'events_count': events_count,
                                                    'entries_count': entries_count, 'audio_sources_count': audio_sources_count,
                                                    'new_sources': new_sources, 'related_sites': related_sites,
                                                    'welcome_text': welcome_text})
コード例 #8
0
def index_stats_and_stuff():
    events_count = Event.all_visible.filter().count()
    entries_count = Entry.all_visible.filter().count()
    audio_sources_count = AudioSource.all_visible.filter().count()

    new_sources = []
    new_sources.extend(AudioSource.all_visible.order_by('-created_date')[:5])
    new_sources.extend(ImageSource.all_visible.order_by('-created_date')[:5])
    new_sources.sort(
        key=lambda source: source.created_date or timezone.datetime(
            1900, 1, 1, tzinfo=timezone.get_current_timezone()),
        reverse=True)
    new_sources = new_sources[:5]

    related_sites = RelatedSite.objects.all()

    welcome_text = get_config('index_hello')

    return {
        'events_count': events_count,
        'entries_count': entries_count,
        'audio_sources_count': audio_sources_count,
        'new_sources': new_sources,
        'related_sites': related_sites,
        'welcome_text': welcome_text
    }
コード例 #9
0
ファイル: transcode_all.py プロジェクト: piyushd26/palanaeum
    def handle(self, *args, **options):
        audio_sources = AudioSource.objects.all()
        cnt = audio_sources.count()
        self.stdout.write(
            "Starting to transcode {} audio sources.".format(cnt))
        logger.info("Starting to transcode %s audio sources.", cnt)
        for i, audio_source in enumerate(audio_sources, start=1):
            self.stdout.write("\tTranscoding source {}/{}: {}".format(
                i, cnt, audio_source.title))
            logger.debug("Transcoding source %s/%s: %s", i, cnt,
                         audio_source.title)
            if not os.path.isfile(
                    audio_source.raw_file.path
            ) and audio_source.status == AudioSource.STORED_IN_CLOUD:
                try:
                    cloud = get_cloud_backend()
                    cloud.download_source(audio_source)
                except Exception as e:
                    logger.exception(str(e))
                    continue
            subprocess.run([
                "ffmpeg", "-y", "-i",
                str(audio_source.raw_file.path), "-b:a",
                get_config('audio_quality'),
                str(audio_source.transcoded_file.path)
            ],
                           stderr=subprocess.DEVNULL,
                           stdout=subprocess.DEVNULL,
                           check=True)
            if audio_source.status == AudioSource.STORED_IN_CLOUD:
                os.unlink(audio_source.raw_file.path)

        self.stdout.write("Successfully transcoded {} sources.".format(cnt))
        logger.info("Successfully transcoded %s sources.", cnt)

        snippets = Snippet.objects.select_related('source').all()
        cnt = snippets.count()
        self.stdout.write("Starting to transcode {} snippets.".format(cnt))
        logger.info("Starting to transcode %s snippets.", cnt)

        for i, snippet in enumerate(snippets, start=1):
            self.stdout.write("\tTranscoding snippet {}/{}...".format(i, cnt))
            logger.debug("Transcoding snippet %s/%s", i, cnt)
            subprocess.run([
                "ffmpeg", "-y", "-ss",
                str(snippet.beginning), "-i",
                str(snippet.source.file.path), "-t",
                str(snippet.length),
                str(snippet.file)
            ],
                           stdout=subprocess.DEVNULL,
                           stderr=subprocess.DEVNULL,
                           check=True)
        self.stdout.write("Successfully transcoded {} snippets.".format(cnt))
        logger.info("Successfully transcoded %s snippets.", cnt)
        self.stdout.write("Done.")
        return
コード例 #10
0
def upload_images_page(request, event_id):
    """
    Display a page with Fine Uploader that will upload the ImageSources.
    """
    event = get_object_or_404(Event, pk=event_id)
    limit = get_config('image_size_limit')
    readable_limit = "{:4.2f} MB".format(limit)
    return render(request, 'palanaeum/staff/upload_images_page.html',
                  {'event': event, 'file_size_limit': limit * 1024 * 1024,
                   'readable_limit': readable_limit})
コード例 #11
0
ファイル: feeds.py プロジェクト: piyushd26/palanaeum
class RecentEntriesFeed(EntryFeed):
    title = "%s - Recent Entries" % get_config('page_title')
    link = "/recent/"

    def items(self):
        entry_ids = Entry.all_visible.order_by('-created').values_list(
            'id', flat=True)[:10]
        entries_map = Entry.prefetch_entries(entry_ids, show_unapproved=False)
        return [
            entry
            for entry in (entries_map[entry_id] for entry_id in entry_ids)
            if entry.last_version is not None
        ]
コード例 #12
0
    def get_page_length(request):
        """
        Returns a preferred page length from session, database or default config.
        """
        session_page_length = request.session.get('page_length', None)
        if session_page_length is not None:
            return int(session_page_length)

        if request.user.is_authenticated:
            db_page_length = UserSettings.objects.get(
                user=request.user).page_length
        else:
            db_page_length = get_config('default_page_length')
        request.session['page_length'] = db_page_length
        return db_page_length
コード例 #13
0
ファイル: b2.py プロジェクト: piyushd26/palanaeum
    def __init__(self, app_id, app_secret):
        super(B2, self).__init__(app_id, app_secret)
        self.auth_token = ''
        self.api_url = ''
        self.download_url = ''
        self.account_id = ''
        self.recommended_part_size = 0

        self._authorize()

        self.bucket_id = get_config('cloud_b2_bucket_id')
        self.bucket_name = ''

        if not self._check_bucket_exists(self.bucket_id):
            raise ConfigurationError
コード例 #14
0
def register_user(request):
    """
    Display a registration form. If it's a POST request, create a new user.
    """
    if request.method == 'POST':
        form = UserCreationFormWithEmail(request.POST)
        if form.is_valid():
            new_user = form.save(commit=True)
            settings = UserSettings.objects.create(user=new_user)
            settings.page_length = get_config('default_page_length')
            settings.save()
            # We're not gonna play with e-mail confirmation and activation for now.
            messages.success(request, _('Congratulations! You have created a new account. '
                                        'You can sign in using your credentials.'))
            logging.getLogger('palanaeum.auth').info("User %s has registered.", request.user)
            return redirect('auth_login')
    else:
        form = UserCreationFormWithEmail()

    return render(request, 'palanaeum/auth/register.html', {'form': form})
コード例 #15
0
class UserSettings(models.Model):
    """
    Objects of this class contain settings for different users, like their e-mail preferences, timezone etc.
    """
    class Meta:
        verbose_name = _('user_settings')
        verbose_name_plural = _('user_settings')

    user = models.OneToOneField(User,
                                related_name='settings',
                                on_delete=models.CASCADE)
    timezone = models.CharField(max_length=32,
                                choices=zip(
                                    pytz.common_timezones,
                                    map(lambda tz: tz.replace('_', ' '),
                                        pytz.common_timezones)),
                                default='UTC')
    page_length = models.IntegerField(
        default=lambda: get_config('default_page_length'),
        verbose_name=_("Preferred page length"))
    website = models.URLField(verbose_name=_('Your website'), blank=True)

    @staticmethod
    def get_page_length(request):
        """
        Returns a preferred page length from session, database or default config.
        """
        session_page_length = request.session.get('page_length', None)
        if session_page_length is not None:
            return int(session_page_length)

        if request.user.is_authenticated:
            db_page_length = UserSettings.objects.get(
                user=request.user).page_length
        else:
            db_page_length = get_config('default_page_length')
        request.session['page_length'] = db_page_length
        return db_page_length
コード例 #16
0
ファイル: tasks.py プロジェクト: piyushd26/palanaeum
def upload_new_sources_to_cloud():
    """
    Looks for new audio sources that are already transcoded and sends them to cloud.
    Could also delete their original file from local drive, depending on configuration.
    """
    cloud = get_cloud_backend()

    if cloud is None:
        return

    sources_to_upload = AudioSource.objects.filter(status=AudioSource.READY,
                                                   is_approved=True)

    for source in sources_to_upload:
        try:
            logger.info("Uploading source %s to cloud.", source)
            cloud.upload_source(source)
            logger.info("Uploading source %s finished.", source)
            if not get_config('audio_keep_original_file'):
                os.unlink(source.raw_file.path)
                logger.info("File %s deleted.", source.raw_file.path)
        except PalanaeumCloudError:
            logger.exception("An error occurred while uploading source %s",
                             source)
コード例 #17
0
ファイル: b2.py プロジェクト: piyushd26/palanaeum
 def test_configuration(cls):
     app_id = get_config('cloud_login')
     app_passwd = get_config('cloud_passwd')
     bucket_id = get_config('cloud_b2_bucket_id')
コード例 #18
0
ファイル: feeds.py プロジェクト: piyushd26/palanaeum
 def title(self, event):
     return "%s - %s" % (get_config('page_title'), event.name)
コード例 #19
0
sources = AudioSource.objects.filter(raw_file__contains='(')
b2 = get_cloud_backend()
for source in sources:
    try:
        b2.download_file(b2._url_encode(source.raw_file), source.raw_file.path)
    except DownloadError:
        b2.download_file(source.raw_file, source.raw_file.path)

import subprocess
for source in sources:
    print(source)
    subprocess.run([
        "ffmpeg", "-y", "-i",
        str(source.raw_file.path), "-b:a",
        get_config('audio_quality'),
        str(source.transcoded_file.path)
    ],
                   stderr=subprocess.DEVNULL,
                   stdout=subprocess.DEVNULL,
                   check=True)

cnt = Snippet.objects.filter(source__in=sources).count()
for i, snippet in enumerate(Snippet.objects.filter(source__in=sources),
                            start=1):
    print("\tTranscoding snippet {}/{}...".format(i, cnt))
    subprocess.run([
        "ffmpeg", "-y", "-ss",
        str(snippet.beginning), "-i",
        str(snippet.source.file.path), "-t",
        str(snippet.length),
コード例 #20
0
js_info_dict = {
    'packages': ('palanaeum',),
}

password_change_kwargs = {
    'template_name': 'palanaeum/auth/password_change.html',
    'success_url': reverse_lazy('auth_password_change_done')
}

password_reset_kwargs = {
    'template_name': 'palanaeum/auth/password_reset.html',
    'email_template_name': 'palanaeum/email/password_reset.html',
    'subject_template_name': 'palanaeum/email/password_reset_subject.html',
    'success_url': reverse_lazy('auth_password_reset_done'),
    'extra_context': {'site_name': get_config('page_title')}
}

password_reset_done_kwargs = {
    'template_name': 'palanaeum/auth/password_reset_done.html'
}

password_reset_confirm_kwargs = {
    'template_name': 'palanaeum/auth/password_reset_complete.html',
    'success_url': reverse_lazy('auth_password_reset_complete')
}

urlpatterns = [
    path('', views.index, name='index'),
    path('jsi18n/', JavaScriptCatalog.as_view(**js_info_dict), name='jsi18n'),
    path('about/', views.about, name='about_page'),
コード例 #21
0
ファイル: tasks.py プロジェクト: piyushd26/palanaeum
def transcode_source(audio_source_id: int):
    """
    Transcode the uploaded file to MP3 constant bit rate format.
    This will prevent issues with browsers wrongly estimating big audio file
    duration.
    """
    try:
        audio_source = AudioSource.objects.get(pk=audio_source_id)
    except AudioSource.DoesNotExist:
        logger.info(
            "Didn't find AudioSource %s in database, maybe it wasn't committed yet. "
            "Retrying in 2 seconds...", audio_source_id)
        time.sleep(2)
        try:
            logger.info("Trying again to find AudioSource %s...",
                        audio_source_id)
            audio_source = AudioSource.objects.get(pk=audio_source_id)
        except AudioSource.DoesNotExist:
            logger.error("AudioSource %s wasn't found in the database.",
                         audio_source_id)
            return

    if audio_source.status != AudioSource.WAITING:
        logger.error(
            "AudioSource %s is not waiting to be processed. Aborting.",
            audio_source_id)
        return

    if not os.path.isfile(
            audio_source.raw_file.path
    ) and audio_source.status == AudioSource.STORED_IN_CLOUD:
        cloud = get_cloud_backend()
        cloud.download_source(audio_source)

    if not os.path.isfile(audio_source.raw_file.path):
        logger.error("Raw file for %s is missing!", audio_source)

    path, ext = os.path.splitext(str(audio_source.raw_file))

    new_path = path + '.mp3'
    new_full_path = os.path.join(settings.MEDIA_ROOT, new_path)
    i = 1
    while os.path.exists(new_full_path):
        new_path = "".join([path] + ['-transcoded'] * i + ['.mp3'])
        i += 1
        new_full_path = os.path.join(settings.MEDIA_ROOT, new_path)

    try:
        audio_source.status = AudioSource.PROCESSING
        audio_source.save()

        logger.info("Starting to transcode AudioSource %s - %s.",
                    audio_source_id, audio_source.title)
        subprocess.run([
            "ffmpeg", "-y", "-i",
            str(audio_source.file.path), "-b:a",
            get_config('audio_quality'),
            str(new_full_path)
        ],
                       stderr=subprocess.DEVNULL,
                       stdout=subprocess.DEVNULL,
                       check=True)

        audio_source.transcoded_file = str(new_path)
        # Make sure that the file wasn't deleted.
        if not AudioSource.objects.filter(pk=audio_source_id).exists():
            logger.error(
                "The AudioSource %s has been deleted, before transcoding was completed.",
                audio_source_id)
            return
        audio_source.status = AudioSource.READY
        audio_source.save()
    except Exception as e:
        logger.exception("Failed to transcode AudioSource %s!",
                         audio_source_id)
        audio_source.status = AudioSource.FAILED
        audio_source.save()
        raise e
    finally:
        if audio_source.status == AudioSource.STORED_IN_CLOUD and not get_config(
                'audio_keep_original_file'):
            os.unlink(audio_source.raw_file.path)

    logger.info("Transcoding of AudioSource %s finished.", audio_source_id)
    return
コード例 #22
0
def favicon(size):
    return configuration.get_config('favicon{}'.format(size)) or static(
        'palanaeum/img/favicon{}.png'.format(size))
コード例 #23
0
ファイル: urls.py プロジェクト: mhalverson/palanaeum
js_info_dict = {
    'packages': ('palanaeum', ),
}

password_change_kwargs = {
    'template_name': 'palanaeum/auth/password_change.html',
    'success_url': reverse_lazy('auth_password_change_done')
}

password_reset_kwargs = {
    'template_name': 'palanaeum/auth/password_reset.html',
    'email_template_name': 'palanaeum/email/password_reset.html',
    'subject_template_name': 'palanaeum/email/password_reset_subject.html',
    'success_url': reverse_lazy('auth_password_reset_done'),
    'extra_context': {
        'site_name': get_config('page_title')
    }
}

password_reset_done_kwargs = {
    'template_name': 'palanaeum/auth/password_reset_done.html'
}

password_reset_confirm_kwargs = {
    'template_name': 'palanaeum/auth/password_reset_complete.html',
    'success_url': 'auth_password_reset_complete'
}

urlpatterns = [
    path('', views.index, name='index'),
    path('jsi18n/', JavaScriptCatalog.as_view(**js_info_dict), name='jsi18n'),
コード例 #24
0
ファイル: models.py プロジェクト: Palanaeum/palanaeum
def get_default_page_length():
    return get_config(('default_page_length'))