From 99ced73816d9425086ae5d8a0d0b919da23a9c13 Mon Sep 17 00:00:00 2001 From: Marc Aymerich Date: Wed, 10 Feb 2016 12:07:55 +0000 Subject: [PATCH] Added support for Moodle WebApp --- orchestra/contrib/saas/backends/moodle.py | 1 + orchestra/contrib/systemusers/actions.py | 3 +- orchestra/contrib/systemusers/forms.py | 9 +- .../systemusers/systemuser/create_link.html | 2 +- orchestra/contrib/webapps/backends/moodle.py | 97 +++++++++++++++++++ orchestra/contrib/webapps/settings.py | 1 + orchestra/contrib/webapps/types/moodle.py | 18 ++++ orchestra/contrib/websites/backends/moodle.py | 25 +++++ 8 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 orchestra/contrib/webapps/backends/moodle.py create mode 100644 orchestra/contrib/webapps/types/moodle.py create mode 100644 orchestra/contrib/websites/backends/moodle.py diff --git a/orchestra/contrib/saas/backends/moodle.py b/orchestra/contrib/saas/backends/moodle.py index b4d28477..cdda0d92 100644 --- a/orchestra/contrib/saas/backends/moodle.py +++ b/orchestra/contrib/saas/backends/moodle.py @@ -60,6 +60,7 @@ class MoodleMuBackend(ServiceController): chown %(user)s:%(user)s %(moodledata_path)s export SITE=%(site_name)s CHANGE_PASSWORD=0 + # TODO su moodle user php %(moodle_path)s/admin/cli/install_database.php \\ --fullname="%(site_name)s" \\ --shortname="%(site_name)s" \\ diff --git a/orchestra/contrib/systemusers/actions.py b/orchestra/contrib/systemusers/actions.py index 8cefff51..24194ae1 100644 --- a/orchestra/contrib/systemusers/actions.py +++ b/orchestra/contrib/systemusers/actions.py @@ -96,7 +96,8 @@ def create_link(modeladmin, request, queryset): base_home = cleaned_data['base_home'] extension = cleaned_data['home_extension'] target = os.path.join(base_home, extension) - link_name = cleaned_data['link_name'] or os.path.join(user.home, os.path.basename(target)) + default_name = os.path.join(user.home, os.path.basename(target)) + link_name = cleaned_data['link_name'] or default_name user.create_link_target = target user.create_link_name = link_name operations.extend(Operation.create_for_action(user, 'create_link')) diff --git a/orchestra/contrib/systemusers/forms.py b/orchestra/contrib/systemusers/forms.py index c2bced5c..c15a9f75 100644 --- a/orchestra/contrib/systemusers/forms.py +++ b/orchestra/contrib/systemusers/forms.py @@ -93,7 +93,8 @@ class LinkForm(forms.Form): base_home = forms.ChoiceField(label=_("Target path"), choices=(), help_text=_("Target link will be under this directory.")) home_extension = forms.CharField(label=_("Home extension"), required=False, initial='', - widget=forms.TextInput(attrs={'size':'70'}), help_text=_("Relative to chosen home.")) + widget=forms.TextInput(attrs={'size':'70'}), + help_text=_("Relative path to chosen directory.")) link_name = forms.CharField(label=_("Link name"), required=False, initial='', widget=forms.TextInput(attrs={'size':'70'}), help_text=_("If left blank or relative path: link will be created in each user home.")) @@ -119,10 +120,12 @@ class LinkForm(forms.Form): if link_name: if link_name.startswith('/'): if len(self.queryset) > 1: - raise ValidationError(_("Link name can not be a full path when multiple users.")) + raise ValidationError( + _("Link name can not be a full path when multiple users.")) link_names = [os.path.dirname(link_name)] else: - link_names = [os.path.join(user.home, os.path.dirname(link_names)) for user in self.queryset] + dir_name = os.path.dirname(link_name) + link_names = [os.path.join(user.home, dir_name) for user in self.queryset] validate_paths_exist(self.instance, link_names) return link_name diff --git a/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/create_link.html b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/create_link.html index 70816c4f..7fb44bc3 100644 --- a/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/create_link.html +++ b/orchestra/contrib/systemusers/templates/admin/systemusers/systemuser/create_link.html @@ -30,7 +30,7 @@
{% block introduction %} - Create link for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %}. + Create simbolic link for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %}. {% endblock %}
    {{ display_objects | unordered_list }}
{% csrf_token %} diff --git a/orchestra/contrib/webapps/backends/moodle.py b/orchestra/contrib/webapps/backends/moodle.py new file mode 100644 index 00000000..04161cfa --- /dev/null +++ b/orchestra/contrib/webapps/backends/moodle.py @@ -0,0 +1,97 @@ +import os +import textwrap + +from django.utils.translation import ugettext_lazy as _ + +from orchestra.contrib.orchestration import ServiceController, replace + +from .. import settings + +from . import WebAppServiceMixin + + +class MoodleBackend(WebAppServiceMixin, ServiceController): + """ + Installs the latest version of Moodle available on download.moodle.org + """ + verbose_name = _("Moodle") + model = 'webapps.WebApp' + default_route_match = "webapp.type == 'moodle-php'" + doc_settings = (settings, + ('WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',) + ) + + + def save(self, webapp): + context = self.get_context(webapp) + self.append(textwrap.dedent("""\ + if [[ $(ls "%(app_path)s" | wc -l) -gt 1 ]]; then + echo "App directory not empty." 2> /dev/null + exit 0 + fi + mkdir -p %(app_path)s + # Prevent other backends from writting here + touch %(app_path)s/.lock + # Weekly caching + moodle_date=$(date -r $(readlink %(cms_cache_dir)s/moodle) +%%s || echo 0) + if [[ $moodle_date -lt $(($(date +%%s)+7*24*60*60)) ]]; then + moodle_url=$(wget https://download.moodle.org/releases/latest/ -O - -q \\ + | tr ' ' '\\n' \\ + | grep 'moodle-latest.*.tgz"' \\ + | sed -E 's#href="([^"]+)".*#\\1#' \\ + | head -n 1 \\ + | sed "s#download.php/#download.php/direct/#") + filename=${moodle_url##*/} + wget $moodle_url -O - --no-check-certificate \\ + | tee %(cms_cache_dir)s/$filename \\ + | tar -xzvf - -C %(app_path)s --strip-components=1 + rm -f %(cms_cache_dir)s/moodle + ln -s %(cms_cache_dir)s/$filename %(cms_cache_dir)s/moodle + else + tar -xzvf %(cms_cache_dir)s/moodle -C %(app_path)s --strip-components=1 + fi + mkdir %(app_path)s/moodledata && { + chmod 750 %(app_path)s/moodledata + echo -n 'order deny,allow\\ndeny from all' > %(app_path)s/moodledata/.htaccess + } + if [[ ! -e %(app_path)s/config.php ]]; then + cp %(app_path)s/config-dist.php %(app_path)s/config.php + sed -i "s#dbtype\s*= '.*#dbtype = '%(db_type)s';#" %(app_path)s/config.php + sed -i "s#dbhost\s*= '.*#dbhost = '%(db_host)s';#" %(app_path)s/config.php + sed -i "s#dbname\s*= '.*#dbname = '%(db_name)s';#" %(app_path)s/config.php + sed -i "s#dbuser\s*= '.*#dbuser = '%(db_user)s';#" %(app_path)s/config.php + sed -i "s#dbpass\s*= '.*#dbpass = '%(password)s';#" %(app_path)s/config.php + sed -i "s#dataroot\s*= '.*#dataroot = '%(app_path)s/moodledata';#" %(app_path)s/config.php + sed -i "s#wwwroot\s*= '.*#wwwroot = '%(www_root)s';#" %(app_path)s/config.php + + fi + rm %(app_path)s/.lock + chown -R %(user)s:%(group)s %(app_path)s + su %(user)s --shell /bin/bash << 'EOF' + php %(app_path)s/admin/cli/install_database.php \\ + --fullname="%(site_name)s" \\ + --shortname="%(site_name)s" \\ + --adminpass="%(password)s" \\ + --adminemail="%(email)s" \\ + --non-interactive \\ + --agree-license \\ + --allow-unstable + EOF + """) % context + ) + + def get_context(self, webapp): + context = super(MoodleBackend, self).get_context(webapp) + contents = webapp.content_set.all() + context.update({ + 'db_type': 'mysqli', + 'db_name': webapp.data['db_name'], + 'db_user': webapp.data['db_user'], + 'password': webapp.data['password'], + 'db_host': settings.WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST, + 'email': webapp.account.email, + 'site_name': "%s Courses" % webapp.account.get_full_name(), + 'cms_cache_dir': os.path.normpath(settings.WEBAPPS_CMS_CACHE_DIR), + 'www_root': contents[0].website.get_absolute_url() if contents else 'http://empty' + }) + return replace(context, '"', "'") diff --git a/orchestra/contrib/webapps/settings.py b/orchestra/contrib/webapps/settings.py index 1e64f31d..ccb437de 100644 --- a/orchestra/contrib/webapps/settings.py +++ b/orchestra/contrib/webapps/settings.py @@ -79,6 +79,7 @@ WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', ( 'orchestra.contrib.webapps.types.misc.WebalizerApp', 'orchestra.contrib.webapps.types.misc.SymbolicLinkApp', 'orchestra.contrib.webapps.types.wordpress.WordPressApp', + 'orchestra.contrib.webapps.types.moodle.MoodleApp', 'orchestra.contrib.webapps.types.python.PythonApp', ), # lazy loading diff --git a/orchestra/contrib/webapps/types/moodle.py b/orchestra/contrib/webapps/types/moodle.py new file mode 100644 index 00000000..9d61450a --- /dev/null +++ b/orchestra/contrib/webapps/types/moodle.py @@ -0,0 +1,18 @@ +from django.utils.translation import ugettext_lazy as _ + +from .cms import CMSApp + + +class MoodleApp(CMSApp): + name = 'moodle-php' + verbose_name = "Moodle" + help_text = _( + "This installs the latest version of Moodle into the webapp directory.
" + "A database and database user will automatically be created for this webapp.
" + "This installer creates a user 'admin' with a randomly generated password.
" + "The password will be visible in the 'password' field after the installer has finished." + ) + icon = 'orchestra/icons/apps/Moodle.png' + + def get_detail(self): + return self.instance.data.get('php_version', '') diff --git a/orchestra/contrib/websites/backends/moodle.py b/orchestra/contrib/websites/backends/moodle.py new file mode 100644 index 00000000..e9a52380 --- /dev/null +++ b/orchestra/contrib/websites/backends/moodle.py @@ -0,0 +1,25 @@ +import textwrap + +from orchestra.contrib.orchestration import ServiceController + + +class MoodleWWWRootBackend(ServiceController): + """ + Configures Moodle site WWWRoot, without it Moodle refuses to work. + """ + verbose_name = "Moodle WWWRoot (required)" + model = 'websites.Content' + default_route_match = "content.webapp.type == 'moodle-php'" + + def save(self, content): + context = self.get_context(content) + self.append(textwrap.dedent("""\ + sed -i "s#wwwroot\s*= '.*#wwwroot = '%(url)s';#" %(app_path)s/config.php + """) % context + ) + + def get_context(self, content): + return { + 'url': content.get_absolute_url(), + 'app_path': content.webapp.get_path(), + }