Skip to content

Commit

Permalink
Openshift (#291)
Browse files Browse the repository at this point in the history
* Test signbank with openshift

* Fix database settings

* add psycopg2-binary

* test django-storages[s3]

* fix typo

* set defaults for STATIC_ & MEDIA_URL

* set bucket name, disable querystring_auth, comment custom_domain

* add default values to bucket settings

* uppercase settings

* include flatpages migrations

* set default acl public-read

* test with s3storage for videos

* Test fixing get_valid_name

* Add temp debug to fix issues

* fix issue of no base_url

* use url in url, not path

* adjust valid_name by removing media

* adjust file renaming

* Try default url

* use storage.move instead of copying files around

* remove templatetag urlencode from video and poster files

* fix videofile_modified_date

* modify renaming logic

* remove unneeded url function for glossvideostorage

* upload files to media directory

* convert canvas to blob to fix insecure access to file from another origin

* add crossorigin attr to video in gloss detail

* Revert "convert canvas to blob to fix insecure access to file from another origin"

This reverts commit 3192cf9.

* fix video update to work with xmlhttpreq

* fix modified_date

* fix incorrectly named s3storage function

* fix video exists check in tools

* fix DB_IS_PSQL check when it is not set
  • Loading branch information
henrinie authored Jun 13, 2024
1 parent 548bf91 commit 0bd37ce
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 60 deletions.
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python:3.11-slim-bookworm

RUN apt update && export DEBIAN_FRONTEND=noninteractive && apt upgrade -y && apt install -y gettext

WORKDIR /usr/src/app

RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"

COPY requirements.txt requirements_os.txt ./
RUN pip install --no-cache-dir -r requirements.txt -r requirements_os.txt

COPY . .

RUN python bin/openshift.py compilemessages

USER nobody:0

EXPOSE 8080

ENTRYPOINT ["./docker-entrypoint.sh"]
13 changes: 13 additions & 0 deletions bin/openshift.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/venv/bin python
import os
import sys

if __name__ == "__main__":
sys.path.append(os.path.dirname(os.path.dirname(__file__)))

os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "signbank.settings.openshift")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
40 changes: 40 additions & 0 deletions custom_migrations/flatpages/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.2.18 on 2024-05-09 10:33

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('sites', '0002_alter_domain_unique'),
]

operations = [
migrations.CreateModel(
name='FlatPage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.CharField(db_index=True, max_length=100, verbose_name='URL')),
('title', models.CharField(max_length=200, verbose_name='title')),
('title_fi', models.CharField(max_length=200, null=True, verbose_name='title')),
('title_sv', models.CharField(max_length=200, null=True, verbose_name='title')),
('title_en', models.CharField(max_length=200, null=True, verbose_name='title')),
('content', models.TextField(blank=True, verbose_name='content')),
('content_fi', models.TextField(blank=True, null=True, verbose_name='content')),
('content_sv', models.TextField(blank=True, null=True, verbose_name='content')),
('content_en', models.TextField(blank=True, null=True, verbose_name='content')),
('enable_comments', models.BooleanField(default=False, verbose_name='enable comments')),
('template_name', models.CharField(blank=True, help_text='Example: “flatpages/contact_page.html”. If this isn’t provided, the system will use “flatpages/default.html”.', max_length=70, verbose_name='template name')),
('registration_required', models.BooleanField(default=False, help_text='If this is checked, only logged-in users will be able to view the page.', verbose_name='registration required')),
('sites', models.ManyToManyField(to='sites.Site', verbose_name='sites')),
],
options={
'verbose_name': 'flat page',
'verbose_name_plural': 'flat pages',
'db_table': 'django_flatpage',
'ordering': ['url'],
},
),
]
Empty file.
6 changes: 6 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

set -e
# Skip while testing
# python bin/openshift.py migrate
gunicorn --bind 0.0.0.0:8080 signbank.wsgi_openshift
3 changes: 3 additions & 0 deletions requirements_os.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
gunicorn~=22.0.0
psycopg2-binary==2.9.9
django-storages[s3]==1.14.3
8 changes: 4 additions & 4 deletions signbank/dictionary/templates/dictionary/gloss_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,12 @@ <h4>{% blocktrans%}Dataset{% endblocktrans %}: <span class="dataset-{{gloss.data
<h4 class="edit edit_text" id="video_title{{glossvideo.pk}}">{{glossvideo.title}}</h4>
</div>
<div class="embed-responsive embed-responsive-16by9">
<video id="glossvideo-{{glossvideo.pk}}" class="embed-responsive-item" width="450" preload="metadata" controls muted
{% if glossvideo.posterfile %} poster="{{glossvideo.posterfile.url|urlencode}}"{% endif %}>
<video id="glossvideo-{{glossvideo.pk}}" class="embed-responsive-item" width="450" preload="metadata" crossorigin="anonymous" controls muted
{% if glossvideo.posterfile %} poster="{{glossvideo.posterfile.url}}"{% endif %}>
{% if glossvideo.get_extension == '.mp4' %}
<source src="{{glossvideo.videofile.url|urlencode}}" type="video/mp4">
<source src="{{glossvideo.videofile.url}}" type="video/mp4">
{% elif glossvideo.get_extension == '.webm' %}
<source src="{{glossvideo.videofile.url|urlencode}}" type="video/webm">
<source src="{{glossvideo.videofile.url}}" type="video/webm">
{% endif %}
{% blocktrans %}Your browser does not support the video tag.{% endblocktrans %}
</video>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ <h4>{{glossvideo.title}}</h4>
</header>
<div class="embed-responsive embed-responsive-16by9">
<video id="glossvideo-{{glossvideo.pk}}" preload="metadata" controls muted
{% if glossvideo.posterfile %} poster="{{glossvideo.posterfile.url|urlencode}}"{% endif %}>
{% if glossvideo.posterfile %} poster="{{glossvideo.posterfile.url}}"{% endif %}>
{% if glossvideo.get_extension == '.mp4' %}
<source src="{{glossvideo.videofile.url|urlencode}}" type="video/mp4">
<source src="{{glossvideo.videofile.url}}" type="video/mp4">
{% elif glossvideo.get_extension == '.webm' %}
<source src="{{glossvideo.videofile.url|urlencode}}" type="video/webm">
<source src="{{glossvideo.videofile.url}}" type="video/webm">
{% endif %}
{% blocktrans %}Your browser does not support the video tag.{% endblocktrans %}
</video>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,12 @@ <h5><strong>{% blocktrans %}Copyright and license:{% endblocktrans %}</strong></
{% if obj.glossvideo_set.all.count > 0 %}style="background-color:rgb(33,33,33);"{% endif %}>
{% if obj.glossvideo_set.all.0 %}
<video id="glossvideo-{{obj.glossvideo_set.all.0.pk}}" class="video-public" preload="metadata" muted
{% if obj.glossvideo_set.all.0.posterfile %} poster="{{obj.glossvideo_set.all.0.posterfile.url|urlencode}}"{% endif %}
{% if obj.glossvideo_set.all.0.posterfile %} poster="{{obj.glossvideo_set.all.0.posterfile.url}}"{% endif %}
onclick="this.paused?this.play():this.pause();" playsinline>
{% if obj.glossvideo_set.all.0.get_extension == '.mp4' %}
<source src="{{obj.glossvideo_set.all.0.videofile.url|urlencode}}" type="video/mp4">
<source src="{{obj.glossvideo_set.all.0.videofile.url}}" type="video/mp4">
{% elif obj.glossvideo_set.all.0.get_extension == '.webm' %}
<source src="{{obj.glossvideo_set.all.0.videofile.url|urlencode}}" type="video/webm">
<source src="{{obj.glossvideo_set.all.0.videofile.url}}" type="video/webm">
{% endif %}
{% blocktrans %}Your browser does not support the video tag.{% endblocktrans %}
</video>
Expand Down
256 changes: 256 additions & 0 deletions signbank/settings/openshift.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
"""OpenShift production environment specific settings for FinSL-signbank."""
import os

from django.utils.translation import ugettext_lazy as _

#: IMPORTANT: Debug should always be False in production
DEBUG = True # TODO: Switch back

# The following settings are defined in environment variables:
# SECRET_KEY, ADMINS, DATABASES, EMAIL_HOST, EMAIL_PORT, DEFAULT_FROM_EMAIL
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'default-secret-key')
#: IMPORTANT: The hostname that this signbank runs on, this prevents HTTP Host header attacks
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT'),
}
}
ADMINS = os.environ.get('DJANGO_ADMINS')

# Absolute path to the base directory of the application.
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# Path to the project directory.
PROJECT_DIR = os.path.dirname(BASE_DIR)
# Sets the field to automatically use for model primary key
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

# A list in the same format as ADMINS that specifies who should get broken link notifications
# when BrokenLinkEmailsMiddleware is enabled. ADMINS are set in secret_settings.
try:
MANAGERS = ADMINS
except NameError:
MANAGERS = (("", ""),)

#: A string representing the time zone for this installation.
TIME_ZONE = 'Europe/Helsinki'

#: A string representing the language code for this installation. This should be in standard language ID format.
#: For example, U.S. English is "en-us".
LANGUAGE_CODE = 'fi'

# The ID, as an integer, of the current site in the django_site database table.
SITE_ID = 1
#: A boolean that specifies whether Django's translation system should be enabled.
USE_I18N = True
#: A boolean that specifies if localized formatting of data will be enabled by default or not.
USE_L10N = True
#: A boolean that specifies if datetimes will be timezone-aware by default or not.
USE_TZ = True
#: A list of all available languages.
#: The list is a list of two-tuples in the format (language code, language name) - for example, ('ja', 'Japanese').
LANGUAGES = (
('fi', _('Finnish')),
('sv', _('Swedish')),
('en', _('English')),
)

# URL to use when referring to static files located in STATIC_ROOT.
# Example: "/static/" or "http://static.example.com/"
STATIC_URL = os.environ.get('STATIC_URL', '/static/')
#: The list of finder backends that know how to find static files in various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)

#: A list of middleware classes to use. The order of middleware classes is critical!
MIDDLEWARE = [
# If want to use some of the HTTPS settings in secret_settings, enable SecurityMiddleware
#'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'reversion.middleware.RevisionMiddleware',
]

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# insert your TEMPLATE_DIRS here
os.path.join(PROJECT_DIR, 'templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
# Insert your TEMPLATE_CONTEXT_PROCESSORS here
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.template.context_processors.csrf',
],
},
},
]

#: A list of authentication backend classes (as strings) to use when attempting to authenticate a user.
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
)

# A list of IP addresses, as strings: Allow the debug() context processor to add some variables to the template context.
INTERNAL_IPS = ('127.0.0.1',)

# A string representing the full Python import path to your root URLconf. For example: "mydjangoapps.urls".
ROOT_URLCONF = 'signbank.urls'


#: A list of strings designating all applications that are enabled in this Django installation.
#: Dotted Python path to: an application configuration class (preferred), or a package containing an application.
#: The order of the apps matter!
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'modeltranslation',
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.staticfiles',
'bootstrap3',
'django_summernote',
'signbank.dictionary',
'django.contrib.flatpages',
'signbank.contentpages',
'signbank.video',
'reversion',
'tagging',
'django_comments',
'guardian',
'notifications',
'django.contrib.sitemaps',
)

# TODO: Evaluate how to handle flatpages migrations
MIGRATION_MODULES = {
'flatpages': 'custom_migrations.flatpages',
}

ABSOLUTE_URL_OVERRIDES = {
#: Allow using admin change url for notifications.
'auth.user': lambda user: "/admin/auth/user/%s/change/" % user.id,
}

#: Location for upload of videos relative to MEDIA_ROOT, videos are stored here prior to copying over to the main
#: storage location
VIDEO_UPLOAD_LOCATION = "upload"

#: How many days a user has until activation time expires. Django-registration related setting.
ACCOUNT_ACTIVATION_DAYS = 7
#: A boolean indicating whether registration of new accounts is currently permitted.
REGISTRATION_OPEN = True

#: The URL where requests are redirected after login when the contrib.auth.login view gets no next parameter.
LOGIN_REDIRECT_URL = '/'

# For django-tagging: force tags to be lowercase.
FORCE_LOWERCASE_TAGS = True

import mimetypes
mimetypes.add_type("video/mp4", ".mov", True)
mimetypes.add_type("video/webm", ".webm", True)

# A list of directories where Django looks for translation files.
LOCALE_PATHS = (
'./locale',
)

DEFAULT_FILE_STORAGE = "storages.backends.s3.S3Storage"
STATICFILES_STORAGE = "storages.backends.s3.S3Storage"

AWS_STORAGE_BUCKET_NAME = os.environ.get("BUCKET_NAME", "")
AWS_S3_ENDPOINT_URL = os.environ.get("BUCKET_ENDPOINT_URL", "")
AWS_S3_REGION_NAME = os.environ.get("BUCKET_REGION_NAME", "")
# custom_domain = os.environ.get("BUCKET_DOMAIN")
AWS_QUERYSTRING_AUTH = False
AWS_DEFAULT_ACL = "public-read"

#: The absolute path to the directory where collectstatic will collect static files for deployment.
#: Example: "/var/www/example.com/static/"
STATIC_ROOT = '/static/'
# This setting defines the additional locations the staticfiles app will traverse if the FileSystemFinder finder
# is enabled, e.g. if you use the collectstatic or findstatic management command or use the static file serving view.
STATICFILES_DIRS = (
os.path.join(PROJECT_DIR, "signbank", "static"),
)

#: Use Local-memory caching for specific views (if you have bigger needs, use something else).
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'finsl-signbank-localmemcache',
}
}

#: Absolute filesystem path to the directory that will hold user-uploaded files.
MEDIA_ROOT = '/media/'
# URL that handles the media served from MEDIA_ROOT, used for managing stored files.
# It must end in a slash if set to a non-empty value.
MEDIA_URL = os.environ.get('MEDIA_URL', '/media/')

#: Location and URL for uploaded files.
UPLOAD_ROOT = MEDIA_ROOT + "upload/"
UPLOAD_URL = MEDIA_URL + "upload/"

#: The backend to use for sending emails.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

#: A sample logging configuration. The only tangible logging
#: performed by this configuration is to send an email to
#: the site admins on every HTTP 500 error when DEBUG=False.
#: See http://docs.djangoproject.com/en/stable/topics/logging for
#: more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

#: Turn off lots of logging.
DO_LOGGING = False
LOG_FILENAME = "debug.log"
Loading

0 comments on commit 0bd37ce

Please sign in to comment.