Commit 7b3a3538 authored by Artem Baranovskyi's avatar Artem Baranovskyi
Browse files

General Plugin Implementation.

- creating ASYST API on Flask server
- preparation a query to API
- response processing and Manual Grading Form manipulations (not yet covers slots !!!).
parent ec9210c4
...@@ -44,7 +44,7 @@ RUN python3 -m venv /opt/myenv ...@@ -44,7 +44,7 @@ RUN python3 -m venv /opt/myenv
RUN /opt/myenv/bin/python3 -m pip install --upgrade pip RUN /opt/myenv/bin/python3 -m pip install --upgrade pip
#RUN /opt/myenv/bin/python3 -m pip install matplotlib Flask torch sklearn-learn #RUN /opt/myenv/bin/python3 -m pip install matplotlib Flask torch sklearn-learn
COPY requirements.txt /opt/myenv/ COPY requirements.txt /opt/myenv/
COPY sentence-transformers-paraphrase-multilingual-MiniLM-L12-v2 /var/www/html/moodle/sentence-transformers-paraphrase-multilingual-MiniLM-L12-v2
WORKDIR /var/www/html/moodle WORKDIR /var/www/html/moodle
RUN /opt/myenv/bin/python3 -m pip install -r /opt/myenv/requirements.txt RUN /opt/myenv/bin/python3 -m pip install -r /opt/myenv/requirements.txt
RUN /opt/myenv/bin/python3 -m pip install --upgrade setuptools wheel RUN /opt/myenv/bin/python3 -m pip install --upgrade setuptools wheel
...@@ -80,11 +80,11 @@ RUN chown -R www-data:www-data ${MOODLE_BASE_DIR} && \ ...@@ -80,11 +80,11 @@ RUN chown -R www-data:www-data ${MOODLE_BASE_DIR} && \
chmod -R 755 ${MOODLE_BASE_DIR_DATA} chmod -R 755 ${MOODLE_BASE_DIR_DATA}
# Copying of beeing developed Plugin # Copying of beeing developed Plugin
COPY yourplugin ${MOODLE_BASE_DIR}/mod/yourplugin COPY asystgrade ${MOODLE_BASE_DIR}/local/asystgrade
# Setting correct acces rules for Plugin # Setting correct acces rules for Plugin
#RUN chown -R www-data:www-data ${MOODLE_BASE_DIR}/mod/yourplugin && \ #RUN chown -R www-data:www-data ${MOODLE_BASE_DIR}/local/asystgrade && \
RUN chmod -R 755 ${MOODLE_BASE_DIR}/mod/yourplugin RUN chmod -R 755 ${MOODLE_BASE_DIR}/local/asystgrade
# Making Symlink for MariaDB Socket # Making Symlink for MariaDB Socket
RUN ln -s /run/mysqld/mysqld.sock /tmp/mysql.sock RUN ln -s /run/mysqld/mysqld.sock /tmp/mysql.sock
...@@ -96,8 +96,8 @@ RUN echo \ ...@@ -96,8 +96,8 @@ RUN echo \
"<VirtualHost *:80>\n" \ "<VirtualHost *:80>\n" \
"ServerName www.moodle.loc\n" \ "ServerName www.moodle.loc\n" \
"ServerAlias www.moodle.loc\n" \ "ServerAlias www.moodle.loc\n" \
"DocumentRoot /var/www/html/moodle\n" \ "DocumentRoot ${MOODLE_BASE_DIR}\n" \
"<Directory /var/www/html/moodle>\n" \ "<Directory ${MOODLE_BASE_DIR}>\n" \
" Options +FollowSymlinks\n" \ " Options +FollowSymlinks\n" \
" AllowOverride All\n" \ " AllowOverride All\n" \
" Require all granted\n" \ " Require all granted\n" \
...@@ -124,8 +124,8 @@ RUN echo \ ...@@ -124,8 +124,8 @@ RUN echo \
"<VirtualHost *:443>\n" \ "<VirtualHost *:443>\n" \
"ServerName www.moodle.loc\n" \ "ServerName www.moodle.loc\n" \
"ServerAlias www.moodle.loc\n" \ "ServerAlias www.moodle.loc\n" \
"DocumentRoot /var/www/html/moodle\n" \ "DocumentRoot ${MOODLE_BASE_DIR}\n" \
"<Directory /var/www/html/moodle>\n" \ "<Directory ${MOODLE_BASE_DIR}>\n" \
" Options +FollowSymlinks\n" \ " Options +FollowSymlinks\n" \
" AllowOverride All\n" \ " AllowOverride All\n" \
" Require all granted\n" \ " Require all granted\n" \
...@@ -148,6 +148,9 @@ RewriteRule ^(.*)$ https://www.moodle.loc/$1 [R,L] \n\ ...@@ -148,6 +148,9 @@ RewriteRule ^(.*)$ https://www.moodle.loc/$1 [R,L] \n\
# Enable SSL module in Apache # Enable SSL module in Apache
RUN a2enmod ssl RUN a2enmod ssl
# Adding entry to /etc/hosts
#RUN echo "127.0.0.1 ${MOODLE_WWWROOT##https://}" >> /etc/hosts
#Opening ports #Opening ports
EXPOSE 80 443 5000 EXPOSE 80 443 5000
......
# api.py # api.py
from flask import Flask, jsonify from flask import Flask, jsonify, request
import os
import sys
# TODO: Adding correct path to ASYST script run_LR_SBERT.py # Adding a path of module to system path
# from run_LR_SBERT import main sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'asyst/Source/Skript/german')))
# sys.path.append(os.path.join(os.path.dirname(__file__), 'asyst/Source/Skript/german')) from run_LR_SBERT import process_data
app = Flask(__name__) app = Flask(__name__)
@app.route('/api/data', methods=['GET']) @app.route('/api/autograde', methods=['POST'])
def get_data(): def get_data():
# Using path to data and model try:
data_path = '/var/www/html/moodle/asyst/Source/Skript/outputs/test.tsv' data = request.get_json()
model_dir = '/var/www/html/moodle/asyst/Source/Skript/german/models' if not data:
return jsonify({"error": "No data provided"}), 400
# Obtaining results from run_asyst function results = process_data(data)
# results => run_asyst(data_path, model_dir)
# Demo dummy API output
results = {
'message': 'Hello from Python API!',
'data': {
'key1': 'value1',
'key2': 'value2'
}
}
# Returning result in JSON format
return jsonify(results) return jsonify(results)
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True) app.run(host='0.0.0.0', port=5000, debug=True)
\ No newline at end of file
...@@ -21,69 +21,25 @@ __source__ = "https://pypi.org/project/sentence-transformers/0.3.0/" ...@@ -21,69 +21,25 @@ __source__ = "https://pypi.org/project/sentence-transformers/0.3.0/"
def main(): def process_data(data):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# Where are we?
location = ".";
if getattr(sys, 'frozen', False):
# running in a bundle
location = sys._MEIPASS
# Required parameters
parser.add_argument(
"--data",
#default=None,
default="/var/www/html/moodle/asyst/Source/Skript/outputs/test.tsv",
type=str,
required=False,
help="The input data file for the task.",
)
parser.add_argument(
"--output_dir",
# default=None,
default="/var/www/html/moodle/asyst/Source/Skript/outputs",
type=str,
required=False,
help="The output directory where predictions will be written.",
)
parser.add_argument( parser.add_argument(
"--model_dir", "--model_dir",
# default=None, # default=None,
default=location+"/Skript/german/models", default="/var/www/html/moodle/asyst/Source/Skript/german/models",
type=str, type=str,
required=False, required=False,
help="The directory where the ML models are stored.", help="The directory where the ML models are stored.",
) )
parser.add_argument(
"--transformer_model_dir",
default="/var/www/html/moodle/sentence-transformers-paraphrase-multilingual-MiniLM-L12-v2",
type=str,
required=False,
help="The directory where the SentenceTransformer model is stored.",
)
args = parser.parse_args()
# open a log file next to the executable with line buffering
# out = open("log.txt", "a",buffering=1);
# print("Started German processing in", location, file=out);
# import SentenceTransformer-model
start_time = time.time()
# print("Reading from", args.data, file=out); args = parser.parse_args()
with open(args.data) as ft:
dft = pd.read_csv(ft, delimiter='\t')
# Sentences we want sentence embeddings for referenceAnswer = data['referenceAnswer']
sentences1_test = dft['referenceAnswer'].values.tolist() studentAnswers = data['studentAnswers']
sentences2_test = dft['studentAnswer'].values.tolist()
# print("Input read:", sentences2_test, file=out);
# Use BERT for mapping tokens to embeddings # Use BERT for mapping tokens to embeddings
word_embedding_model = models.Transformer(args.transformer_model_dir) word_embedding_model = models.Transformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')
# pooling operation can choose by setting true (Apply mean pooling to get one fixed sized sentence vector) # pooling operation can choose by setting true (Apply mean pooling to get one fixed sized sentence vector)
pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(), pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension(),
pooling_mode_mean_tokens=True, pooling_mode_mean_tokens=True,
...@@ -92,59 +48,31 @@ def main(): ...@@ -92,59 +48,31 @@ def main():
# compute the sentence embeddings for both sentences # compute the sentence embeddings for both sentences
model = SentenceTransformer(modules=[word_embedding_model, pooling_model]) model = SentenceTransformer(modules=[word_embedding_model, pooling_model])
# print("Model loaded", file=out);
sentence_embeddings1_test = model.encode(sentences1_test, convert_to_tensor=True, show_progress_bar=False)
# print("Embeddings RefA:", sentence_embeddings1_test, file=out);
sentence_embeddings2_test = model.encode(sentences2_test, convert_to_tensor=True, show_progress_bar=False)
# print("Embeddings found", file=out);
# Possible concatenations from the embedded sentences can be selected
def similarity(sentence_embeddings1, sentence_embeddings2):
# I2=(|u − v| + u ∗ v)
simi = abs(np.subtract(sentence_embeddings1, sentence_embeddings2)) + np.multiply(sentence_embeddings1,
sentence_embeddings2)
return simi
# calls the similarity function and get the concatenated values between the sentence embeddings
computed_simis_test = similarity(sentence_embeddings1_test, sentence_embeddings2_test)
# get the sentence embeddings and the labels for train and test sentence_embeddings1 = model.encode([referenceAnswer] * len(studentAnswers), convert_to_tensor=True, show_progress_bar=False)
sentence_embeddings2 = model.encode(studentAnswers, convert_to_tensor=True, show_progress_bar=False)
computed_simis_test = similarity(sentence_embeddings1, sentence_embeddings2)
X_test = computed_simis_test X_test = computed_simis_test
# Y_test = np.array(dft['label'])
# UP: read pre-trained LR model # UP: read pre-trained LR model
clf_log = pickle.load(open("/var/www/html/moodle/asyst/Source/Skript/german/models/clf_BERT.pickle", "rb")) clf_log = pickle.load(open("/var/www/html/moodle/asyst/Source/Skript/german/models/clf_BERT.pickle", "rb"))
# print('--------Evaluate on Testset------- ', file=out)
predictions = clf_log.predict(X_test) predictions = clf_log.predict(X_test)
# UP print results
with open(args.output_dir + "/predictions.txt", "w") as writer:
# TODO: write results to plugins DB Table
writer.write("question\treferenceAnswer\tstudentAnswer\tsuggested grade\tobserved grade\n")
for i in range(len(dft)):
hrpred = "incorrect"
if predictions[i] == 1:
hrpred = "correct"
writer.write(
str(dft.iloc[i][0])
+ "\t"
+ str(dft.iloc[i][1])
+ "\t"
+ str(dft.iloc[i][2])
+ "\t"
+ str(hrpred)
+ "\t"
+ str(dft.iloc[i][3])
+ "\n"
)
# print('\nExecution time:', time.strftime("%H:%M:%S", time.gmtime(time.time() - start_time)), file=out) results = []
for i in range(len(studentAnswers)):
result = {
"predicted_grade": "correct" if predictions[i] == 1 else "incorrect"
}
results.append(result)
return results
if __name__ == "__main__": # Possible concatenations from the embedded sentences can be selected
main() def similarity(sentence_embeddings1, sentence_embeddings2):
# I2=(|u − v| + u ∗ v)
simi = abs(np.subtract(sentence_embeddings1, sentence_embeddings2)) + np.multiply(sentence_embeddings1,
sentence_embeddings2)
return simi
\ No newline at end of file
<?php
namespace local_asystgrade\api;
defined('MOODLE_INTERNAL') || die();
class client {
private $endpoint;
private $httpClient;
public function __construct(string $endpoint, http_client $httpClient = null) {
$this->endpoint = $endpoint;
$this->httpClient = $httpClient ?: new http_client();
}
public function send_data($data) {
$response = $this->httpClient->post($this->endpoint, $data);
return $response;
}
}
\ No newline at end of file
<?php
namespace local_asystgrade\api;
defined('MOODLE_INTERNAL') || die();
class http_client implements http_client_interface {
public function post($url, $data) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception('Curl error: ' . curl_error($ch));
}
curl_close($ch);
if ($response === false) {
throw new \Exception('Error sending data to API');
}
return $response;
}
}
<?php
namespace local_asystgrade\api;
defined('MOODLE_INTERNAL') || die();
interface http_client_interface {
public function post($url, $data);
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/yourplugin/db" VERSION="2024032201" COMMENT="XMLDB file for mod yourplugin plugin" <XMLDB PATH="local/asystgrade/db" VERSION="2024032201" COMMENT="XMLDB file for mod asystgrade plugin"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd" xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
> >
<TABLES> <TABLES>
<TABLE NAME="mod_yourplugin" COMMENT="Table for storing yourplugin data"> <TABLE NAME="local_asystgrade" COMMENT="Table for storing asystgrade data">
<FIELDS> <FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"/> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"/>
<FIELD NAME="question" TYPE="text" NOTNULL="true"/> <FIELD NAME="question" TYPE="text" NOTNULL="true"/>
......
<?php <?php
/** /**
* You may have settings in your plugin * You may localized strings in your plugin
* *
* @package local_yourplugin * @package local_asystgrade
* @copyright 2024 Artem Baranovskyi * @copyright 2024 Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html gnu gpl v3 or later * @license http://www.gnu.org/copyleft/gpl.html gnu gpl v3 or later
*/ */
defined('MOODLE_INTERNAL') || die(); $string['pluginname'] = 'ASYST API Moodle integration plugin';
$string['apiendpoint'] = 'API Endpoint';
if ($ADMIN->fulltree) { $string['apiendpoint_desc'] = 'The endpoint of the AsystGrade API.';
}
\ No newline at end of file
<?php
/**
* @package local_asystgrade
* @author Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//use Exception;
//use local_asystgrade\api\client;
//require_once $dirroot . 'local_asystgrade\api\client.php';
defined('MOODLE_INTERNAL') || die();
/**
* A hook function that will process the data and insert the rating value.
* The function must be called on the desired page like https://www.moodle.loc/mod/quiz/report.php?id=2&mode=grading&slot=1&qid=1&grade=needsgrading&includeauto=1
*
* @return void
*/
function local_asystgrade_before_footer()
{
global $PAGE, $DB;
// Получение параметров из URL
$qid = optional_param('qid', null, PARAM_INT);
$slot = optional_param('slot', false, PARAM_INT);
if ($PAGE->url->compare(new moodle_url('/mod/quiz/report.php'), URL_MATCH_BASE) && $slot) {
$question_attempts = $DB->get_recordset(
'question_attempts',
[
'questionid' => $qid,
'slot' => $slot
],
'',
'*'
);
// Obtaining exemplary answer
$referenceAnswer = $DB->get_record(
'qtype_essay_options',
[
'questionid' => $qid
],
'*',
MUST_EXIST
)->graderinfo;
$studentAnswers = [];
foreach ($question_attempts as $question_attempt) {
// Получение всех шагов для данного questionusageid
$quizattempt_steps = $DB->get_recordset(
'question_attempt_steps',
[
'questionattemptid' => $question_attempt->id
],
'',
'*'
);
// Processing every quiz attempt step
foreach ($quizattempt_steps as $quizattempt_step) {
if ($quizattempt_step->state === 'complete') {
$userid = $quizattempt_step->userid;
$attemptstepid = $quizattempt_step->id;
// Obtaining student's answer
$studentAnswer = $DB->get_record(
'question_attempt_step_data',
[
'attemptstepid' => $attemptstepid,
'name' => 'answer'
],
'*',
MUST_EXIST
)->value;
// Forming student's answers array
$studentAnswers[] = $studentAnswer;
error_log("User ID: $userid, Student Answer: $studentAnswer, Reference Answer: $referenceAnswer");
}
}
// Closing of record's sets
$quizattempt_steps->close();
}
// Closing of record's sets
$question_attempts->close();
// API request preparation
$data = [
'referenceAnswer' => $referenceAnswer,
'studentAnswers' => $studentAnswers
];
error_log('Data prepared: ' . print_r($data, true));
// Obtaining API settings
$apiendpoint = get_config('local_asystgrade', 'apiendpoint');
if (!$apiendpoint) {
$apiendpoint = 'http://127.0.0.1:5000/api/autograde'; // Default setting
}
error_log('APIendpoint: ' . $apiendpoint);
// Initializing API client
try {
$apiClient = new \local_asystgrade\api\client($apiendpoint);
error_log('ApiClient initiated.');
// Sending data on API and obtaining auto grades
error_log('Sending data to API and getting grade');
$response = $apiClient->send_data($data);
$grades = json_decode($response, true);
error_log('Grade obtained: ' . print_r($grades, true));
} catch (Exception $e) {
error_log('Error sending data to API: ' . $e->getMessage());
return;
}
error_log('After API call');
// Check grades existence and pasting them at grade input fields through JavaScript DOM manipulations
$script = "
<script type='text/javascript'>
document.addEventListener('DOMContentLoaded', function() {";
foreach ($grades as $index => $grade) {
if (isset($grade['predicted_grade'])) {
$predicted_grade = $grade['predicted_grade'] == 'correct' ? 1 : 0;
// How forms param name="q2:1_-mark" see at https://github.com/moodle/moodle/blob/main/question/behaviour/rendererbase.php#L132
// and https://github.com/moodle/moodle/blob/main/question/engine/questionattempt.php#L381 , L407
// TODO: fix question attempt -> ID and question attempt -> step
$input_name = "q" . ($index + 2) . ":1_-mark"; // Q is an question attempt -> ID of mdl_quiz_attempts, :1_ is question attempt -> step
$script .= "
console.log('Trying to update input: {$input_name} with grade: {$predicted_grade}');
var gradeInput = document.querySelector('input[name=\"{$input_name}\"]');
if (gradeInput) {
console.log('Found input: {$input_name}');
gradeInput.value = '{$predicted_grade}';
} else {
console.log('Input not found: {$input_name}');
}";
}
}
$script .= "
});
</script>";
echo $script;
error_log('URL matches /mod/quiz/report.php in page_init');
}
}
spl_autoload_register(function ($classname) {
// Check if the class name starts with our plugin's namespace
if (strpos($classname, 'local_asystgrade\\') === 0) {
// Преобразуем пространство имен в путь
$classname = str_replace('local_asystgrade\\', '', $classname);
$classname = str_replace('\\', DIRECTORY_SEPARATOR, $classname);
$filepath = __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . $classname . '.php';
if (file_exists($filepath)) {
require_once($filepath);
}
}
});
<?php
/**
* You may have settings in your plugin
*
* @package local_asystgrade
* @copyright 2024 Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html gnu gpl v3 or later
*/
defined('MOODLE_INTERNAL') || die(); // security check, only internall call through Moodle allowed.
if ($hassiteconfig) {
global $ADMIN;
// Ensure the settings page is created under the correct location in the site admin menu.
$ADMIN->fulltree = true;
// Create a new settings page for your plugin.
$settings = new admin_settingpage('local_asystgrade', get_string('pluginname', 'local_asystgrade'));
// Add the settings page to the admin tree.
$ADMIN->add('localplugins', $settings);
// Add your settings here.
$settings->add(new admin_setting_configtext(
'local_asystgrade/apiendpoint',
get_string('apiendpoint', 'local_asystgrade'),
get_string('apiendpoint_desc', 'local_asystgrade'),
'',
PARAM_URL
));
}
\ No newline at end of file
<?php
/**
* Version details.
*
* @package local_asystgrade
* @copyright 2024 Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_asystgrade'; // Full name of the plugin.
$plugin->version = 2024032201; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2022041900; // Requires Moodle version 3.11
//$plugin->cron = 0;
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '1.0';
$plugin->dependencies = [
'mod_quiz' => ANY_VERSION, // This plugin depends on the quiz module
];
\ No newline at end of file
...@@ -7,37 +7,32 @@ services: ...@@ -7,37 +7,32 @@ services:
env_file: env_file:
- ./.env - ./.env
volumes: volumes:
# - moodledata:${MOODLE_BASE_DIR_DATA}
- mariadb_data:/var/lib/mysql - mariadb_data:/var/lib/mysql
ports: ports:
- 3306:3306 - 3306:3306
networks: networks:
- network # Добавление к сети - network # Adding the network communication between containers
environment: environment:
- MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD} - MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
- MOODLE_DATABASE_USER=${MOODLE_DATABASE_USER} # Пользователь базы данных - MOODLE_DATABASE_USER=${MOODLE_DATABASE_USER}
- MOODLE_DATABASE_PASSWORD=${MOODLE_DATABASE_PASSWORD} # Пароль пользователя базы данных - MOODLE_DATABASE_PASSWORD=${MOODLE_DATABASE_PASSWORD}
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-u", "root", "--password=${MARIADB_ROOT_PASSWORD}" ]
interval: 10s
timeout: 5s
retries: 5
moodle: moodle:
env_file: env_file:
- ./.env - ./.env
restart: always restart: always
build: # Собираем свой образ Moodle из Dockerfile+. build: # Building own Moodle container from Dockerfile.
context: . # Контекст сборки (где находится Dockerfile+.) context: . # The context of the build (where is Dockerfile)
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- MOODLE_BASE_DIR=${MOODLE_BASE_DIR} - MOODLE_BASE_DIR=${MOODLE_BASE_DIR}
- MOODLE_BASE_DIR_DATA=${MOODLE_BASE_DIR_DATA} - MOODLE_BASE_DIR_DATA=${MOODLE_BASE_DIR_DATA}
- MOODLE_DATABASE_TYPE=mariadb - MOODLE_DATABASE_TYPE=mariadb
- MOODLE_DATABASE_HOST=${MOODLE_DATABASE_HOST} - MOODLE_DATABASE_HOST=${MOODLE_DATABASE_HOST}
- MOODLE_DATABASE_NAME=${MOODLE_DATABASE_NAME} # Имя базы данных - MOODLE_DATABASE_NAME=${MOODLE_DATABASE_NAME}
- MOODLE_DATABASE_USER=${MOODLE_DATABASE_USER} # Пользователь базы данных - MOODLE_DATABASE_USER=${MOODLE_DATABASE_USER}
- MOODLE_DATABASE_PASSWORD=${MOODLE_DATABASE_PASSWORD} # Пароль пользователя базы данных - MOODLE_DATABASE_PASSWORD=${MOODLE_DATABASE_PASSWORD}
ports: ports:
- 80:80 - 80:80
- 443:443 - 443:443
...@@ -48,21 +43,21 @@ services: ...@@ -48,21 +43,21 @@ services:
depends_on: # Dependency from MariaDB service depends_on: # Dependency from MariaDB service
- mariadb - mariadb
volumes: volumes:
# - ./yourplugin:${MOODLE_BASE_DIR}/mod/yourplugin # Синхронизируем папку плагина с контейнером - ${PWD}/asystgrade:${MOODLE_BASE_DIR}/local/asystgrade # Sync the plugin folder with the container
- moodle_plugin:${MOODLE_BASE_DIR}/mod/yourplugin # Синхронизируем папку плагина с контейнером - moodle_data:${MOODLE_BASE_DIR} # Volume for Moodle
- moodle_data:${MOODLE_BASE_DIR} # Том для данных Moodle - moodledata:${MOODLE_BASE_DIR_DATA} # Volume for Moodle data
- moodledata:${MOODLE_BASE_DIR_DATA} # Том для данных Moodle
networks: networks:
- network # Добавление к сети - network # Adding the network communication between containers
volumes:
volumes: # Defining local volumes
moodledata: moodledata:
driver: local # Локальный драйвер томов driver: local
mariadb_data: mariadb_data:
driver: local driver: local
moodle_data: moodle_data:
driver: local driver: local
moodle_plugin: asystgrade:
driver: local driver: local
networks: networks:
network: # Создание пользовате network: # Creating a network
\ No newline at end of file \ No newline at end of file
...@@ -5,7 +5,21 @@ set -a ...@@ -5,7 +5,21 @@ set -a
. .env . .env
set +a set +a
# Ensure correct ownership and permissions before installation
docker-compose exec moodle chown -R www-data:www-data ${MOODLE_BASE_DIR}
docker-compose exec moodle chmod -R 755 ${MOODLE_BASE_DIR}
# Ensure necessary directories in moodledata exist
docker-compose exec moodle mkdir -p ${MOODLE_BASE_DIR_DATA}
docker-compose exec moodle mkdir -p ${MOODLE_BASE_DIR_DATA}/localcache
docker-compose exec moodle mkdir -p ${MOODLE_BASE_DIR_DATA}/sessions
docker-compose exec moodle mkdir -p ${MOODLE_BASE_DIR_DATA}/temp
docker-compose exec moodle mkdir -p ${MOODLE_BASE_DIR_DATA}/trashdir
docker-compose exec moodle chown -R www-data:www-data ${MOODLE_BASE_DIR_DATA}
docker-compose exec moodle chmod -R 775 ${MOODLE_BASE_DIR_DATA}
# Install Moodle # Install Moodle
sleep 5
docker-compose exec moodle php ${MOODLE_BASE_DIR}/admin/cli/install.php \ docker-compose exec moodle php ${MOODLE_BASE_DIR}/admin/cli/install.php \
--wwwroot="${MOODLE_WWWROOT}" \ --wwwroot="${MOODLE_WWWROOT}" \
--dataroot="${MOODLE_BASE_DIR_DATA}" \ --dataroot="${MOODLE_BASE_DIR_DATA}" \
...@@ -20,18 +34,36 @@ docker-compose exec moodle php ${MOODLE_BASE_DIR}/admin/cli/install.php \ ...@@ -20,18 +34,36 @@ docker-compose exec moodle php ${MOODLE_BASE_DIR}/admin/cli/install.php \
--agree-license \ --agree-license \
--non-interactive --non-interactive
#docker-compose exec moodle chown -R www-data:www-data /var/www/html/moodle # Check if database backup exists and restore it if it does
docker-compose exec moodle chmod -R 755 /var/www/html/moodle BACKUP_FILE="moodle_backup.sql"
if [ -f "$BACKUP_FILE" ]; then
# docker-compose exec mariadb apt-get update && apt-get install -y mysql-client && rm -rf /var/lib/apt/lists/*
docker-compose exec mariadb bash -c "apt-get update && apt-get install -y mysql-client && rm -rf /var/lib/apt/lists/*"
echo "Database backup found. Restoring..."
docker-compose exec -T mariadb mysql -u ${MOODLE_DATABASE_USER} -p${MOODLE_DATABASE_PASSWORD} ${MOODLE_DATABASE_NAME} < moodle_backup.sql
echo "Database restored from backup."
else
echo "No database backup found. Skipping restore."
fi
# Set correct access rules for the plugin # Ensure correct ownership and permissions after installation
docker-compose exec moodle chown -R www-data:www-data /var/www/html/moodle/mod/yourplugin docker-compose exec moodle chown -R www-data:www-data ${MOODLE_BASE_DIR}
docker-compose exec moodle chmod -R 775 /var/www/html/moodle/mod/yourplugin docker-compose exec moodle chmod -R 755 ${MOODLE_BASE_DIR}
# Set correct access rules for the plugin
docker-compose exec moodle chown -R www-data:www-data ${MOODLE_BASE_DIR}/local/asystgrade
docker-compose exec moodle chmod -R 775 ${MOODLE_BASE_DIR}/local/asystgrade
sudo chown -R $(whoami):$(whoami) ./asystgrade
# Create the run_sag script file # Create the run_sag script file
docker-compose exec moodle bash -c 'echo "#!/bin/bash" > /usr/local/bin/run_sag' docker-compose exec moodle bash -c 'cat <<EOF > /usr/local/bin/run_sag
docker-compose exec moodle bash -c 'echo ". /opt/myenv/bin/activate" >> /usr/local/bin/run_sag' #!/bin/bash
docker-compose exec moodle bash -c 'echo "cd /var/www/html/moodle/asyst/Source/Skript/german" >> /usr/local/bin/run_sag' . /opt/myenv/bin/activate
docker-compose exec moodle bash -c 'echo "/opt/myenv/bin/python3 /var/www/html/moodle/asyst/Source/Skript/german/run_LR_SBERT.py" >> /usr/local/bin/run_sag' cd ${MOODLE_BASE_DIR}/asyst/Source/Skript/german
/opt/myenv/bin/python3 ${MOODLE_BASE_DIR}/api.py
EOF'
# Make the script executable & run it # Make the script executable & run it
docker-compose exec moodle chmod +x /usr/local/bin/run_sag docker-compose exec moodle chmod +x /usr/local/bin/run_sag
......
This diff is collapsed.
...@@ -5,13 +5,14 @@ nodaemon=true ...@@ -5,13 +5,14 @@ nodaemon=true
command=/usr/sbin/apache2ctl -D FOREGROUND command=/usr/sbin/apache2ctl -D FOREGROUND
autostart=true autostart=true
autorestart=true autorestart=true
stdout_logfile=/var/log/supervisor/flask.log stdout_logfile=/var/log/moodle.log
stderr_logfile=/var/log/supervisor/flask_err.log stderr_logfile=/var/log/moodle_err.log
[program:flask] [program:flask]
command=/bin/bash -c 'source /opt/myenv/bin/activate && FLASK_APP=/var/www/html/moodle/api.py /opt/myenv/bin/flask run --host=0.0.0.0' #command=/bin/bash -c 'source /opt/myenv/bin/activate && FLASK_APP=/var/www/html/moodle/api.py /opt/myenv/bin/flask run --host=0.0.0.0'
command=/bin/bash -c 'source /opt/myenv/bin/activate &&/opt/myenv/bin/python3 /var/www/html/moodle/api.py'
directory=/var/www/html/moodle directory=/var/www/html/moodle
autostart=true autostart=true
autorestart=true autorestart=true
stdout_logfile=/var/log/supervisor/moodle.log stdout_logfile=/var/log/flask.log
stderr_logfile=/var/log/supervisor/moodle_err.log stderr_logfile=/var/log/flask_err.log
\ No newline at end of file
<?php
/**
* You may localized strings in your plugin
*
* @package local_yourplugin
* @copyright 2024 Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html gnu gpl v3 or later
*/
$string['pluginname'] = 'New local plugin';
// Path to the Python 3 executable
$python_executable = '/opt/myenv/bin/python3';
// Path to the moodlemlbackend script to be executed. First we use test API api.py, then run_LR_SBERT.py
$python_script = '/var/www/html/moodle/api.py';
//$python_script = '/var/www/html/moodle/asyst/Source/Skript/german/run_LR_SBERT.py';
// Python command you want to execute
//$python_command = 'print("Hello, world!!!")';
// Execution the command and getting the result
//shell_exec($python_command);
// Formation of a command to execute
//$full_command = $python_executable . ' -c \'' . $python_command . '\'';
//$full_command = $python_executable . ' ' . $python_script;
// Now call the API
$api_url = 'http://127.0.0.1:5000/api/data';
$response = file_get_contents($api_url);
// Output the result (assuming backend returns JSON)
if ($response !== false) {
$data = json_decode($response, true);
if ($data !== null) {
// Data processing
// Example: output results echo "<pre>";
print_r($data);
echo "</pre>";
} else {
echo "Error on data processing from moodlemlbackend!";
}
} else {
echo "Execution error: result is not a string.";
}
<?php
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_yourplugin
* @author Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function mod_yourplugin_before_footer()
{
if (!get_config('mod_yourplugin', 'enabled')) {
return;
}
}
\ No newline at end of file
<?php
/**
* Version details.
*
* @package mod_yourplugin
* @copyright 2024 Artem Baranovskyi
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024032201; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2014050800; // Requires this Moodle version.
$plugin->component = 'mod_yourplugin'; // Full name of the plugin (used for diagnostics).
$plugin->cron = 0;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment