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, }
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})
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)
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})
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)
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
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})
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 }
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
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})
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 ]
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
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
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})
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
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)
def test_configuration(cls): app_id = get_config('cloud_login') app_passwd = get_config('cloud_passwd') bucket_id = get_config('cloud_b2_bucket_id')
def title(self, event): return "%s - %s" % (get_config('page_title'), event.name)
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),
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'),
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
def favicon(size): return configuration.get_config('favicon{}'.format(size)) or static( 'palanaeum/img/favicon{}.png'.format(size))
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'),
def get_default_page_length(): return get_config(('default_page_length'))