【问题标题】:Accessing a Moodle server's API via Python's request library通过 Python 请求库访问 Moodle 服务器 API
【发布时间】:2019-04-09 05:15:26
【问题描述】:

我正在尝试使用 Python 的 requests 库访问 Moodle 安装的 Web 服务。我有 API 的文档和一个用 php 编写的示例项目(我以前没有看过 php,而且比我预期的要难理解),但我真的很难正确格式化请求。该站点返回检测到的无效参数,因此我很确定我的端点、授权令牌和服务器配置正在工作,而这只是让我失望的数据格式。

首先是错误...

<?xml version="1.0" encoding="UTF-8" ?>
<EXCEPTION class="invalid_parameter_exception">
<ERRORCODE>invalidparameter</ERRORCODE>
<MESSAGE>Invalid parameter value detected</MESSAGE>
</EXCEPTION>

现在我的代码...

import requests

target = 'http://example.com/moodle/webservice/rest/server.php?'
moodle_create_token = 'xxx'
moodle_enrol_token = 'yyy'
url_payload = {
    "wstoken":moodle_create_token,
   "wsfunction":"core_user_create_users"
    }

###not sure if I should just be passing this as a dict or some deeper more layered struct
payload = {
    "username":"testuser",
    "password":'testpass',
    "firstname":'testf',
    "lastname":'testl',
    "email":"test@example.com",
    "idnumber":"1234"
}

###not sure how to include the payload as the last argument in the function (currently data=)
###I feel like at this point I've just been throwing random data at it and hoping something sticks haha.
r=requests.post(target, params=url_payload, data=payload)

这是网站的文档

moodle api general structure

moodle api XML-RPC (PHP structure)

moodle api REST (POST parameters)

moodle response format 1

moodle response format 2

最后是php中的例子。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>V6</title>
</head>

<body>

<?php
//load curl.php
require_once('curl.php');

function randomPassword() //according to Moodle password requirements
{
    $part1 = "";
    $part2 = "";
    $part3 = "";

    //alphanumeric LOWER
    $alphabet = "abcdefghijklmnopqrstuwxyz";
    $password_created = array(); //remember to declare $pass as an array
    $alphabetLength = strlen($alphabet) - 1; //put the length -1 in cache
    for ($i = 0; $i < 3; $i++) 
    {
        $pos = rand(0, $alphabetLength); // rand(int $min , int $max)
        $password_created[] = $alphabet[$pos];
    }
    $part1 = implode($password_created); //turn the array into a string
    //echo"<br/>part1 = $part1";

    //alphanumeric UPPER
    $alphabet = "ABCDEFGHIJKLMNOPQRSTUWXYZ";
    $password_created = array(); //remember to declare $pass as an array
    $alphabetLength = strlen($alphabet) - 1; //put the length -1 in cache
    for ($i = 0; $i < 3; $i++) 
    {
        $pos = rand(0, $alphabetLength); // rand(int $min , int $max)
        $password_created[] = $alphabet[$pos];
    }   
    $part2 = implode($password_created); //turn the array into a string
    //echo"<br/>part2 = $part2";

    //alphanumeric NUMBER
    $alphabet = "0123456789";
    $password_created = array(); //remember to declare $pass as an array
    $alphabetLength = strlen($alphabet) - 1; //put the length -1 in cache
    for ($i = 0; $i < 2; $i++) 
    {
        $pos = rand(0, $alphabetLength); // rand(int $min , int $max)
        $password_created[] = $alphabet[$pos];
    }   
    $part3 = implode($password_created); //turn the array into a string
    //echo"<br/>part3 = $part3";

    $password = $part1 . $part2 . $part3 . "#";

    return $password;
}

function getCDate()
{
    $format = "Ymd";
    $fulldate = date($format);  
    //echo"<br/>fulldate = $fulldate";
    return $fulldate;
}

function enrol($user_id, $course_id) 
{
    $role_id = 5; //assign role to be Student

    $domainname = 'http://www.yoursite.eu'; //paste your domain here
    $wstoken = '8486ed14f3ghjec8967a0229d0a28zzz'; //here paste your enrol token 
    $wsfunctionname = 'enrol_manual_enrol_users';

    $enrolment = array( 'roleid' => $role_id, 'userid' => $user_id, 'courseid' => $course_id );
    $enrolments = array($enrolment);
    $params = array( 'enrolments' => $enrolments );

    header('Content-Type: text/plain');
    $serverurl = $domainname . "/webservice/rest/server.php?wstoken=" . $wstoken . "&wsfunction=" . $wsfunctionname;
    $curl = new curl;
    $restformat = ($restformat == 'json')?'&moodlewsrestformat=' . $restformat:'';
    $resp = $curl->post($serverurl . $restformat, $params);
    print_r($resp);
}

function getUserDetails()
{
    $firstname  = "TestUser";
    $lastname   = "TestUser";
    $email      = "TestUser@zzz.gr";
    $city       = "Thessaloniki";
    $country    = "EL";
    $description= "ZZZ";

    //assign username
    //get first two letters of name and surname
    //$strlength_user = strlen($firstname);
    //$strlength_pass = strlen($lastname);
    $rest_firstname = substr($firstname, 0, 2);
    $rest_lastname  = substr($lastname, 0, 2);
    $part1 = $rest_firstname . $rest_lastname;
    $part1 = strtolower($part1);
    //echo"<br/>part1 = $part1";
    $dt = getCDate();
    $part2 = substr($dt, -4);
    //echo"<br/>part2 = $part2";

    $username = $part1 . "." . $part2;
    echo"<br/>Username = $username";

    //assign password
    $password = randomPassword();
    echo"<br/>Password = $password";

    //call WS core_user_create_user of moodle to store the new user
    $domainname = 'http://www.yoursite.eu';
    $wstoken = 'ed1f6d3ebadg372f95f28cd96bd43zzz'; //here paste your create user token 
    $wsfunctionname = 'core_user_create_users';
    //REST return value
    $restformat = 'xml'; 
    //parameters
    $user1 = new stdClass();
    $user1->username    = $username;
    $user1->password    = $password;
    $user1->firstname   = $firstname;
    $user1->lastname    = $lastname;
    $user1->email       = $email;
    $user1->auth        = 'manual';
    $user1->idnumber    = 'numberID';
    $user1->lang        = 'en';
    $user1->city        = $city;
    $user1->country     = $country;
    $user1->description = $description;

    $users = array($user1);
    $params = array('users' => $users);
    //REST call
    header('Content-Type: text/plain');
    $serverurl = $domainname . "/webservice/rest/server.php?wstoken=" . $wstoken . "&wsfunction=" . $wsfunctionname;
    $curl = new curl;
    $restformat = ($restformat == 'json')?'&moodlewsrestformat=' . $restformat:'';
    $resp = $curl->post($serverurl . $restformat, $params);
    print_r($resp);\


    //get id from $resp
    $xml_tree = new SimpleXMLElement($resp);
    print_r($xml_tree);         
    $value = $xml_tree->MULTIPLE->SINGLE->KEY->VALUE;
    $user_id = intval(sprintf("%s",$value));
    echo"<br/>user_id number = $user_id";

    //enrol_manual_enrol_users 
    //for($i = 64; $i < 70; $i++) //where 64,65,66,67,68,69 are the six ids of the six courses of phase 1
    for($i = 64; $i < 65; $i++)
    {
        echo "\nThe user has been successfully enrolled to course " . $i;
        $course_id = $i;
        enrol($user_id, $course_id);
    }   
}

getUserDetails();

?>
</body>
</html>

【问题讨论】:

  • 尝试将data=payload改为json=payload
  • 谢谢我以前试过。我认为问题在于我传递的数据的嵌套。我正在发送单级结构... data = [pair 1, pair 2, pair 3] 我需要做的是更深层次的事情 user=[pair 1, pair 2, pair 3] data = [ user[]] 我只是无法让它完全正常工作。

标签: python rest python-requests moodle moodle-api


【解决方案1】:

这是一个来自mrcinv/moodle_api.py 的示例,它显示了使用 Python 的requests 来访问 Moodle Web 服务 API:

from requests import get, post
# Module variables to connect to moodle api
KEY = "SECRET API KEY"
URL = "https://moodle.site.com"
ENDPOINT="/webservice/rest/server.php"

def rest_api_parameters(in_args, prefix='', out_dict=None):
    """Transform dictionary/array structure to a flat dictionary, with key names
    defining the structure.

    Example usage:
    >>> rest_api_parameters({'courses':[{'id':1,'name': 'course1'}]})
    {'courses[0][id]':1,
     'courses[0][name]':'course1'}
    """
    if out_dict==None:
        out_dict = {}
    if not type(in_args) in (list,dict):
        out_dict[prefix] = in_args
        return out_dict
    if prefix == '':
        prefix = prefix + '{0}'
    else:
        prefix = prefix + '[{0}]'
    if type(in_args)==list:
        for idx, item in enumerate(in_args):
            rest_api_parameters(item, prefix.format(idx), out_dict)
    elif type(in_args)==dict:
        for key, item in in_args.items():
            rest_api_parameters(item, prefix.format(key), out_dict)
    return out_dict

def call(fname, **kwargs):
    """Calls moodle API function with function name fname and keyword arguments.

    Example:
    >>> call_mdl_function('core_course_update_courses',
                           courses = [{'id': 1, 'fullname': 'My favorite course'}])
    """
    parameters = rest_api_parameters(kwargs)
    parameters.update({"wstoken": KEY, 'moodlewsrestformat': 'json', "wsfunction": fname})
    response = post(URL+ENDPOINT, parameters).json()
    if type(response) == dict and response.get('exception'):
        raise SystemError("Error calling Moodle API\n", response)
    return response

class CourseList():
    """Class for list of all courses in Moodle and order them by id and idnumber."""
    def __init__(self):
        # TODO fullname atribute is filtered
        # (no <span class="multilang" lang="sl">)
        courses_data = call('core_course_get_courses')
        self.courses = []
        for data in courses_data:
            self.courses.append(Course(**data))
        self.id_dict = {}
        self.idnumber_dict = {}
        for course in self.courses:
            self.id_dict[course.id] = course
            if course.idnumber:
                self.idnumber_dict[course.idnumber] = course
    def __getitem__(self, key):
        if 0<= key < len(self.courses):
            return self.courses[key]
        else:
            raise IndexError

    def by_id(self, id):
        "Return course with given id."
        return self.id_dict.get(id)

    def by_idnumber(self, idnumber):
        "Course with given idnumber"
        return self.idnumber_dict.get(idnumber)

    def update_courses(courses_to_update, fields):
        "Update a list of courses in one go."
        if not ('id' in fields):
            fields.append('id')
        courses = [{k: c.__dict__[k] for k in fields} for c in courses_to_update]
        return call("core_course_update_courses", 
             courses = courses)

.. 并且还展示了如何为 Course 定义自定义类。以同样的方式,可以为 User、Grades 等创建类。

此外,PyPi 上有一些包装模块,例如moodlemoodle-ws-client

【讨论】:

    【解决方案2】:

    好的,所以我找到了一个可行的解决方案,但我怀疑它有点大杂烩,没有充分利用请求库。

    我所做的是将所有参数作为参数传递给 url。

    target = 'http://example.com/moodle/webservice/rest/server.php'
    moodle_create_token = 'xxx'
    
    payload = {
        "wstoken":moodle_create_token,
        "moodlewsrestformat":"json", #just to get response as json
        "wsfunction":"core_user_create_users",
        "users[0][username]":"testusername",
        "users[0][password]":'testpassword',
        "users[0][firstname]":'testfirstname',
        "users[0][lastname]":'testlastname',
        "users[0][email]":"testemail@example.com",
        "users[0][idnumber]":"0000001"
        }
    
    r=requests.post(target, params=payload)
    

    显然,我通常不会将数据硬编码为字符串,但显然 url 参数的字典列表将是。

    【讨论】:

      【解决方案3】:

      我制作了一个名为moodlepy的python库

      pip install moodlepy
      

      简单好用的例子

      from moodle import Moodle
      
      target = 'http://example.com/moodle/webservice/rest/server.php'
      moodle_create_token = 'xxx'
      
      moodle = Moodle(target, moodle_create_token)
      r = moodle(
          'core_user_create_users',
          username="testusername",
          password='testpassword',
          firstname='testfirstname',
          lastname='testlastname',
          email="testemail@example.com",
          idnumber="0000001"
      )  # return the data (dict, list, etc)
      

      您也可以使用类型化响应,例如调用core_webservice_get_site_info

      site_info = moodle.core.webservice.get_site_info()
      site_info.username
      site_info.version
      

      注意:并非所有功能都已实现(目前)。

      【讨论】:

        猜你喜欢
        • 2015-12-20
        • 1970-01-01
        • 2021-05-02
        • 2021-02-02
        • 2021-09-23
        • 1970-01-01
        • 2023-03-26
        • 1970-01-01
        • 2019-04-23
        相关资源
        最近更新 更多