20. خادم QGIS ولغة Python¶
20.1. المقدمة¶
لمعرفة المزيد عن QGIS Server، اقرأ دليل QGIS Server.
يتكون QGIS Server من ثلاثة أشياء مختلفة:
مكتبة QGIS Server: مكتبة توفر واجهة API لإنشاء خدمات ويب OGC
QGIS Server FCGI: تطبيق ثنائي FCGI هو
qgis_mapserv.fcgiيُنفّذ مع خادم ويب مجموعة من خدمات OGC (مثل WMS وWFS وWCS) وواجهات OGC API (WFS3/OAPIF)خادم التطوير QGIS Development Server: تطبيق ثنائي لخادم تطوير هو
qgis_mapserverيُنفّذ مجموعة من خدمات OGC (مثل WMS وWFS وWCS) وواجهات OGC API (WFS3/OAPIF)
يركّز هذا الفصل من الدليل على الموضوع الأول، ومن خلال شرح استخدام QGIS Server API يبيّن كيف يمكن استخدام Python لتوسيع سلوك الخادم أو تحسينه أو تخصيصه، أو كيف تُستخدم QGIS Server API لتضمين خادم QGIS في تطبيق آخر.
هناك عدة طرق مختلفة يمكنك من خلالها تغيير سلوك QGIS Server أو توسيع قدراته لتقديم خدمات أو واجهات API مخصصة جديدة، وهذه هي السيناريوهات الرئيسية التي قد تواجهها:
EMBEDDINGاستخدام QGIS Server API من تطبيق Python آخرSTANDALONEتشغيل QGIS Server كخدمة WSGI/HTTP مستقلةFILTERSتحسين QGIS Server أو تخصيصه بإضافات المرشِّحات (filter plugins)SERVICESإضافة خدمة جديدة SERVICEOGC APIsإضافة واجهة OGC API جديدة
يتطلب التضمين والتطبيقات المستقلة استخدام QGIS Server Python API مباشرةً من سكربت أو تطبيق Python آخر. أما الخيارات المتبقية فهي أنسب عندما تريد إضافة ميزات مخصصة إلى تطبيق QGIS Server ثنائي قياسي (FCGI أو خادم التطوير): وفي هذه الحالة ستحتاج إلى كتابة إضافة Python لتطبيق الخادم وتسجيل المرشِّحات أو الخدمات أو واجهات API المخصصة الخاصة بك.
20.2. أساسيات Server API¶
الأصناف الأساسية المشاركة في تطبيق QGIS Server نموذجي هي:
QgsServerنسخة الخادم (عادةً نسخة واحدة طوال عمر التطبيق)QgsServerRequestكائن الطلب (يُعاد إنشاؤه عادةً مع كل طلب)QgsServer.handleRequest(request, response)تعالج الطلب وتملأ الاستجابة
يمكن تلخيص سير عمل QGIS Server FCGI أو خادم التطوير على النحو الآتي:
1initialize the QgsApplication
2create the QgsServer
3the main server loop waits forever for client requests:
4 for each incoming request:
5 create a QgsServerRequest request
6 create a QgsServerResponse response
7 call QgsServer.handleRequest(request, response)
8 filter plugins may be executed
9 send the output to the client
داخل الدالة QgsServer.handleRequest(request, response) تُستدعى دوال الاستدعاء الراجع (callbacks) الخاصة بإضافات المرشِّحات، ويُتاح QgsServerRequest و QgsServerResponse للإضافات عبر الصنف QgsServerInterface.
تحذير
أصناف خادم QGIS ليست آمنة للاستخدام مع الخيوط المتعددة (thread safe)، لذا يجب عليك دائمًا استخدام نموذج المعالجة المتعددة (multiprocessing) أو الحاويات عند بناء تطبيقات قابلة للتوسع مبنية على QGIS Server API.
20.3. التطبيقات المستقلة أو التضمين¶
في تطبيقات الخادم المستقلة أو التضمين، ستحتاج إلى استخدام أصناف الخادم المذكورة أعلاه مباشرةً، وتغليفها ضمن تنفيذ لخادم ويب يدير جميع تفاعلات بروتوكول HTTP مع العميل.
فيما يلي مثال بسيط على استخدام QGIS Server API (من دون جزء HTTP):
1from qgis.core import QgsApplication
2from qgis.server import *
3app = QgsApplication([], False)
4
5# Create the server instance, it may be a single one that
6# is reused on multiple requests
7server = QgsServer()
8
9# Create the request by specifying the full URL and an optional body
10# (for example for POST requests)
11request = QgsBufferServerRequest(
12 'http://localhost:8081/?MAP=/qgis-server/projects/helloworld.qgs' +
13 '&SERVICE=WMS&REQUEST=GetCapabilities')
14
15# Create a response objects
16response = QgsBufferServerResponse()
17
18# Handle the request
19server.handleRequest(request, response)
20
21print(response.headers())
22print(response.body().data().decode('utf8'))
23
24app.exitQgis()
إليك مثالًا كاملًا على تطبيق مستقل طُوِّر لاختبارات التكامل المستمر في مستودع الشيفرة المصدرية لـ QGIS، وهو يعرض مجموعة واسعة من مرشِّحات الإضافات وأنظمة المصادقة المختلفة (غير مخصص لبيئة الإنتاج لأنه طُوِّر لأغراض الاختبار فقط، لكنه يبقى مفيدًا للتعلم): qgis_wrapped_server.py
20.4. إضافات الخادم¶
تُحمَّل إضافات Python الخاصة بالخادم مرة واحدة عند بدء تشغيل تطبيق QGIS Server، ويمكن استخدامها لتسجيل المرشِّحات أو الخدمات أو واجهات API.
بنية إضافة الخادم مشابهة جدًا لنظيرتها في تطبيق سطح المكتب؛ إذ يُتاح للإضافات كائن من نوع QgsServerInterface، ويمكن للإضافات تسجيل مرشِّح أو خدمة أو واجهة API مخصصة أو أكثر في السجل المقابل باستخدام إحدى الدوال التي تتيحها واجهة الخادم.
20.4.1. إضافات المرشِّحات للخادم¶
تأتي المرشِّحات بثلاثة أنواع مختلفة، ويمكن إنشاء نسخة منها بالاشتقاق من أحد الأصناف أدناه واستدعاء الدالة المقابلة من QgsServerInterface:
نوع المرشِّح |
الصنف الأساسي |
التسجيل في QgsServerInterface |
|---|---|---|
I/O |
|
|
التحكم في الوصول |
|
|
التخزين المؤقت (Cache) |
|
|
20.4.1.1. مرشِّحات الإدخال/الإخراج¶
يمكن لمرشِّحات الإدخال/الإخراج تعديل مدخلات الخادم ومخرجاته (الطلب والاستجابة) للخدمات الأساسية (مثل WMS وWFS)، مما يتيح إجراء أي نوع من المعالجة على سير عمل الخدمات. فمن الممكن مثلًا تقييد الوصول إلى طبقات محددة، أو حقن ورقة أنماط XSL في استجابة XML، أو إضافة علامة مائية إلى صورة WMS مُولَّدة، وهكذا.
من هذه النقطة، قد تجد من المفيد إلقاء نظرة سريعة على server plugins API docs.
يجب أن يُنفّذ كل مرشِّح واحدة على الأقل من دوال الاستدعاء الراجع الثلاث:
onRequestReady()onResponseComplete()onSendResponse()
تملك جميع المرشِّحات وصولًا إلى كائن الطلب/الاستجابة (QgsRequestHandler)، ويمكنها التعامل مع جميع خصائصه (الإدخال/الإخراج) وإطلاق الاستثناءات (وإن كان ذلك بطريقة خاصة نوعًا ما كما سنرى أدناه).
تُعيد جميع هذه الدوال قيمة منطقية تشير إلى ما إذا كان يجب تمرير الاستدعاء إلى المرشِّحات اللاحقة. فإذا أعادت إحدى هذه الدوال False تتوقف السلسلة، وإلا فسينتقل الاستدعاء إلى المرشِّح التالي.
فيما يلي شيفرة زائفة توضّح كيف يعالج الخادم طلبًا نموذجيًا ومتى تُستدعى دوال الاستدعاء الراجع الخاصة بالمرشِّح:
1for each incoming request:
2 create GET/POST request handler
3 pass request to an instance of QgsServerInterface
4 call onRequestReady filters
5
6 if there is not a response:
7 if SERVICE is WMS/WFS/WCS:
8 create WMS/WFS/WCS service
9 call service’s executeRequest
10 possibly call onSendResponse for each chunk of bytes
11 sent to the client by a streaming services (WFS)
12 call onResponseComplete
13 request handler sends the response to the client
تصف الفقرات التالية دوال الاستدعاء الراجع المتاحة بالتفصيل.
20.4.1.1.1. onRequestReady¶
تُستدعى هذه الدالة عندما يكون الطلب جاهزًا: أي بعد تحليل عنوان URL الوارد والبيانات وقبل الدخول إلى مُبدِّل الخدمات الأساسية (مثل WMS وWFS)، وهذه هي النقطة التي يمكنك فيها التعامل مع المدخلات وتنفيذ إجراءات مثل:
المصادقة/التفويض
إعادة التوجيه
إضافة معاملات معينة أو إزالتها (مثل typenames)
إطلاق الاستثناءات
بل يمكنك استبدال خدمة أساسية بالكامل بتغيير المعامل SERVICE، وبالتالي تجاوز الخدمة الأساسية كليًا (وإن لم يكن لذلك معنى كبير).
20.4.1.1.2. onSendResponse¶
تُستدعى هذه الدالة كلما أُفرغ أي مخرج جزئي من مخزن الاستجابة المؤقت (أي إلى stdout الخاص بـ FCGI إذا كان خادم fcgi مستخدمًا) ومن هناك إلى العميل. ويحدث ذلك عند بث محتوى ضخم (مثل WFS GetFeature). وفي هذه الحالة قد تُستدعى onSendResponse() عدة مرات.
لاحظ أنه إذا لم تكن الاستجابة مبثوثة، فلن تُستدعى onSendResponse() إطلاقًا.
وفي جميع الأحوال، سيُرسل الجزء الأخير (أو الوحيد) إلى العميل بعد استدعاء onResponseComplete().
إعادة False ستمنع إفراغ البيانات إلى العميل. وهذا مرغوب فيه عندما تريد إضافةٌ جمع كل أجزاء الاستجابة وفحصها أو تغيير الاستجابة في onResponseComplete().
20.4.1.1.3. onResponseComplete¶
تُستدعى هذه الدالة مرة واحدة عندما تنهي الخدمات الأساسية (إن طُلبت) معالجتها ويصبح الطلب جاهزًا للإرسال إلى العميل. وكما ذُكر أعلاه، ستُستدعى هذه الدالة قبل إرسال الجزء الأخير (أو الوحيد) من البيانات إلى العميل. وفي خدمات البث، قد تكون onSendResponse() قد استُدعيت عدة مرات.
onResponseComplete() هي المكان المثالي لتقديم تنفيذ لخدمات جديدة (WPS أو خدمات مخصصة) وللتعامل المباشر مع المخرجات القادمة من الخدمات الأساسية (مثل إضافة علامة مائية فوق صورة WMS).
لاحظ أن إعادة False ستمنع الإضافات التالية من تنفيذ onResponseComplete()، لكنها ستمنع في كل الأحوال إرسال الاستجابة إلى العميل.
20.4.1.1.4. إطلاق الاستثناءات من إضافة¶
لا يزال هناك عمل ينبغي إنجازه في هذا الموضوع: يستطيع التنفيذ الحالي التمييز بين الاستثناءات المعالَجة وغير المعالَجة بتعيين خاصية في QgsRequestHandler إلى نسخة من QgsMapServiceException، وبهذه الطريقة تستطيع شيفرة C++ الرئيسية التقاط استثناءات Python المعالَجة وتجاهل الاستثناءات غير المعالَجة (أو الأفضل: تسجيلها في السجل).
يعمل هذا الأسلوب في الأساس، لكنه ليس "بأسلوب Python" تمامًا: فالأسلوب الأفضل هو إطلاق الاستثناءات من شيفرة Python ورؤيتها تتصاعد إلى حلقة C++ لتُعالَج هناك.
20.4.1.1.5. كتابة إضافة للخادم¶
إضافة الخادم هي إضافة Python قياسية لـ QGIS كما هو موضح في تطوير إضافات Python، لكنها توفر واجهة إضافية (أو بديلة) فحسب: فإضافة QGIS النموذجية لسطح المكتب تصل إلى تطبيق QGIS عبر نسخة QgisInterface، أما إضافة الخادم فلا تصل إلا إلى QgsServerInterface عند تنفيذها ضمن سياق تطبيق QGIS Server.
لكي يعرف QGIS Server أن الإضافة تملك واجهة خادم، يلزم إدخال بيانات وصفية خاص (في metadata.txt):
server=True
مهم
لن يُحمّل QGIS Server ولن يُنفّذ إلا الإضافات التي عُيِّنت فيها البيانات الوصفية server=True.
الإضافة المثال qgis3-server-vagrant التي نناقشها هنا (مع إضافات أخرى كثيرة) متاحة على github، كما نُشرت بعض إضافات الخادم في مستودع إضافات QGIS الرسمي.
20.4.1.1.5.1. ملفات الإضافة¶
إليك بنية المجلدات لإضافة الخادم المثال الخاصة بنا.
1PYTHON_PLUGINS_PATH/
2 HelloServer/
3 __init__.py --> *required*
4 HelloServer.py --> *required*
5 metadata.txt --> *required*
20.4.1.1.5.1.1. __init__.py¶
هذا الملف مطلوب لنظام الاستيراد في Python. كما يتطلب QGIS Server أن يحتوي هذا الملف على الدالة serverClassFactory()، التي تُستدعى عند تحميل الإضافة في QGIS Server عند بدء تشغيل الخادم. وتتلقى مرجعًا إلى نسخة من QgsServerInterface، ويجب أن تُعيد نسخة من صنف إضافتك. هكذا يبدو الملف __init__.py للإضافة المثال:
def serverClassFactory(serverIface):
from .HelloServer import HelloServerServer
return HelloServerServer(serverIface)
20.4.1.1.5.1.2. HelloServer.py¶
هنا يحدث السحر، وهكذا يبدو السحر: (مثلًا HelloServer.py)
تتكون إضافة الخادم عادةً من دالة استدعاء راجع واحدة أو أكثر مُغلَّفة في نسخ من QgsServerFilter.
يُنفّذ كل QgsServerFilter واحدة أو أكثر من دوال الاستدعاء الراجع التالية:
onRequestReady()onResponseComplete()onSendResponse()
ينفّذ المثال التالي مرشِّحًا بسيطًا يطبع HelloServer! في حال كان المعامل SERVICE يساوي “HELLO”:
1class HelloFilter(QgsServerFilter):
2
3 def __init__(self, serverIface):
4 super().__init__(serverIface)
5
6 def onRequestReady(self) -> bool:
7 QgsMessageLog.logMessage("HelloFilter.onRequestReady")
8 return True
9
10 def onSendResponse(self) -> bool:
11 QgsMessageLog.logMessage("HelloFilter.onSendResponse")
12 return True
13
14 def onResponseComplete(self) -> bool:
15 QgsMessageLog.logMessage("HelloFilter.onResponseComplete")
16 request = self.serverInterface().requestHandler()
17 params = request.parameterMap()
18 if params.get('SERVICE', '').upper() == 'HELLO':
19 request.clear()
20 request.setResponseHeader('Content-type', 'text/plain')
21 # Note that the content is of type "bytes"
22 request.appendBody(b'HelloServer!')
23 return True
يجب تسجيل المرشِّحات في serverIface كما في المثال التالي:
class HelloServerServer:
def __init__(self, serverIface):
serverIface.registerFilter(HelloFilter(serverIface), 100)
يحدد المعامل الثاني للدالة registerFilter() أولوية تحدد ترتيب دوال الاستدعاء الراجع التي تحمل الاسم نفسه (الأولوية الأدنى تُستدعى أولًا).
باستخدام دوال الاستدعاء الراجع الثلاث، تستطيع الإضافات التعامل مع مدخلات الخادم و/أو مخرجاته بطرق مختلفة كثيرة. وفي كل لحظة، تملك نسخة الإضافة وصولًا إلى QgsRequestHandler عبر QgsServerInterface. ويملك الصنف QgsRequestHandler الكثير من الدوال التي يمكن استخدامها لتعديل معاملات الإدخال قبل الدخول إلى المعالجة الأساسية للخادم (باستخدام requestReady()) أو بعد معالجة الطلب من قِبل الخدمات الأساسية (باستخدام sendResponse()).
تغطي الأمثلة التالية بعض حالات الاستخدام الشائعة:
20.4.1.1.5.2. تعديل المدخلات¶
تحتوي الإضافة المثال على مثال اختباري يغيّر معاملات الإدخال القادمة من سلسلة الاستعلام؛ ففي هذا المثال يُحقن معامل جديد في parameterMap (المحلَّلة مسبقًا)، ويصبح هذا المعامل مرئيًا للخدمات الأساسية (مثل WMS)، وفي نهاية معالجة الخدمات الأساسية نتحقق من أن المعامل لا يزال موجودًا:
1class ParamsFilter(QgsServerFilter):
2
3 def __init__(self, serverIface):
4 super(ParamsFilter, self).__init__(serverIface)
5
6 def onRequestReady(self) -> bool:
7 request = self.serverInterface().requestHandler()
8 params = request.parameterMap( )
9 request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
10 return True
11
12 def onResponseComplete(self) -> bool:
13 request = self.serverInterface().requestHandler()
14 params = request.parameterMap( )
15 if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
16 QgsMessageLog.logMessage("SUCCESS - ParamsFilter.onResponseComplete")
17 else:
18 QgsMessageLog.logMessage("FAIL - ParamsFilter.onResponseComplete")
19 return True
هذا مقتطف مما تراه في ملف السجل:
1 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloServerServer - loading filter ParamsFilter
2 src/core/qgsmessagelog.cpp: 45: (logMessage) [1ms] 2014-12-12T12:39:29 Server[0] Server plugin HelloServer loaded!
3 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 Server[0] Server python plugins loaded
4 src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
5 src/mapserver/qgsserverfilter.cpp: 42: (onRequestReady) [0ms] QgsServerFilter plugin default onRequestReady called
6 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.onResponseComplete
في السطر المميَّز، تشير السلسلة “SUCCESS” إلى أن الإضافة اجتازت الاختبار.
يمكن استغلال الأسلوب نفسه لاستخدام خدمة مخصصة بدلًا من خدمة أساسية: فيمكنك مثلًا تخطي طلب SERVICE من نوع WFS أو أي طلب أساسي آخر بمجرد تغيير المعامل SERVICE إلى شيء مختلف، فتُتخطّى الخدمة الأساسية. ثم يمكنك حقن نتائجك المخصصة في المخرجات وإرسالها إلى العميل (وهذا موضح أدناه).
نصيحة
إذا كنت تريد فعلًا تنفيذ خدمة مخصصة، فمن المستحسن الاشتقاق من QgsService وتسجيل خدمتك في registerFilter() باستدعاء الدالة registerService(service) الخاصة به
20.4.1.1.5.3. تعديل المخرجات أو استبدالها¶
يوضّح مثال مرشِّح العلامة المائية كيفية استبدال مخرجات WMS بصورة جديدة ناتجة عن إضافة صورة علامة مائية فوق صورة WMS التي أنشأتها خدمة WMS الأساسية:
1from qgis.server import *
2from qgis.PyQt.QtCore import *
3from qgis.PyQt.QtGui import *
4
5class WatermarkFilter(QgsServerFilter):
6
7 def __init__(self, serverIface):
8 super().__init__(serverIface)
9
10 def onResponseComplete(self) -> bool:
11 request = self.serverInterface().requestHandler()
12 params = request.parameterMap( )
13 # Do some checks
14 if (params.get('SERVICE').upper() == 'WMS' \
15 and params.get('REQUEST').upper() == 'GETMAP' \
16 and not request.exceptionRaised() ):
17 QgsMessageLog.logMessage("WatermarkFilter.onResponseComplete: image ready %s" % request.parameter("FORMAT"))
18 # Get the image
19 img = QImage()
20 img.loadFromData(request.body())
21 # Adds the watermark
22 watermark = QImage(os.path.join(os.path.dirname(__file__), 'media/watermark.png'))
23 p = QPainter(img)
24 p.drawImage(QRect( 20, 20, 40, 40), watermark)
25 p.end()
26 ba = QByteArray()
27 buffer = QBuffer(ba)
28 buffer.open(QIODevice.WriteOnly)
29 img.save(buffer, "PNG" if "png" in request.parameter("FORMAT") else "JPG")
30 # Set the body
31 request.clearBody()
32 request.appendBody(ba)
33 return True
في هذا المثال تُفحص قيمة المعامل SERVICE، فإذا كان الطلب الوارد من نوع WMS GETMAP ولم تُعيَّن أي استثناءات من قِبل إضافة نُفِّذت سابقًا أو من قِبل الخدمة الأساسية (WMS في هذه الحالة)، تُسترجع الصورة التي أنشأتها WMS من مخزن المخرجات المؤقت وتُضاف إليها صورة العلامة المائية. والخطوة الأخيرة هي مسح مخزن المخرجات المؤقت واستبداله بالصورة المُنشأة حديثًا. لاحظ أنه في الحالات الواقعية ينبغي أيضًا التحقق من نوع الصورة المطلوب بدلًا من دعم PNG أو JPG فقط.
20.4.1.2. مرشِّحات التحكم في الوصول¶
تمنح مرشِّحات التحكم في الوصول المطوّرَ تحكمًا دقيقًا في الطبقات والمعالم والبيانات الوصفية التي يمكن الوصول إليها، ويمكن تنفيذ دوال الاستدعاء الراجع التالية في مرشِّح التحكم في الوصول:
layerFilterExpression(layer)layerFilterSubsetString(layer)layerPermissions(layer)authorizedLayerAttributes(layer, attributes)allowToEdit(layer, feature)cacheKey()
20.4.1.2.1. ملفات الإضافة¶
إليك بنية المجلدات للإضافة المثال الخاصة بنا:
1PYTHON_PLUGINS_PATH/
2 MyAccessControl/
3 __init__.py --> *required*
4 AccessControl.py --> *required*
5 metadata.txt --> *required*
20.4.1.2.1.1. __init__.py¶
هذا الملف مطلوب لنظام الاستيراد في Python. وكما هو الحال في جميع إضافات خادم QGIS، يحتوي هذا الملف على الدالة serverClassFactory()، التي تُستدعى عند تحميل الإضافة في QGIS Server عند بدء التشغيل. وتتلقى مرجعًا إلى نسخة من QgsServerInterface، ويجب أن تُعيد نسخة من صنف إضافتك. هكذا يبدو الملف __init__.py للإضافة المثال:
def serverClassFactory(serverIface):
from MyAccessControl.AccessControl import AccessControlServer
return AccessControlServer(serverIface)
20.4.1.2.1.2. AccessControl.py¶
1class AccessControlFilter(QgsAccessControlFilter):
2
3 def __init__(self, server_iface):
4 super().__init__(server_iface)
5
6 def layerFilterExpression(self, layer):
7 """ Return an additional expression filter """
8 return super().layerFilterExpression(layer)
9
10 def layerFilterSubsetString(self, layer):
11 """ Return an additional subset string (typically SQL) filter """
12 return super().layerFilterSubsetString(layer)
13
14 def layerPermissions(self, layer):
15 """ Return the layer rights """
16 return super().layerPermissions(layer)
17
18 def authorizedLayerAttributes(self, layer, attributes):
19 """ Return the authorised layer attributes """
20 return super().authorizedLayerAttributes(layer, attributes)
21
22 def allowToEdit(self, layer, feature):
23 """ Are we authorised to modify the following geometry """
24 return super().allowToEdit(layer, feature)
25
26 def cacheKey(self):
27 return super().cacheKey()
28
29class AccessControlServer:
30
31 def __init__(self, serverIface):
32 """ Register AccessControlFilter """
33 serverIface.registerAccessControl(AccessControlFilter(serverIface), 100)
يمنح هذا المثال وصولًا كاملًا للجميع.
من مهام الإضافة أن تعرف من الذي سجّل الدخول.
في جميع هذه الدوال، نتلقى الطبقة كوسيط لنتمكن من تخصيص القيود لكل طبقة.
20.4.1.2.2. layerFilterExpression¶
تُستخدم لإضافة تعبير لتقييد النتائج.
على سبيل المثال، لتقييد النتائج بالمعالم التي تساوي فيها السمة role القيمة user.
def layerFilterExpression(self, layer):
return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString¶
مثل السابقة لكنها تستخدم SubsetString (التي تُنفَّذ في قاعدة البيانات)
على سبيل المثال، لتقييد النتائج بالمعالم التي تساوي فيها السمة role القيمة user.
def layerFilterSubsetString(self, layer):
return "role = 'user'"
20.4.1.2.4. layerPermissions¶
تقييد الوصول إلى الطبقة.
تُعيد كائنًا من نوع LayerPermissions()، يملك الخصائص التالية:
canReadلرؤيتها فيGetCapabilitiesوالحصول على صلاحية القراءة.canInsertللتمكن من إدراج معلم جديد.canUpdateللتمكن من تحديث معلم.canDeleteللتمكن من حذف معلم.
على سبيل المثال، لقصر كل شيء على صلاحية القراءة فقط:
1def layerPermissions(self, layer):
2 rights = QgsAccessControlFilter.LayerPermissions()
3 rights.canRead = True
4 rights.canInsert = rights.canUpdate = rights.canDelete = False
5 return rights
20.4.1.2.6. allowToEdit¶
تُستخدم لتقييد التحرير على مجموعة فرعية من المعالم.
تُستخدم في بروتوكول WFS-Transaction.
على سبيل المثال، للتمكن من تحرير المعالم التي تحمل فيها السمة role القيمة user فقط:
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey¶
يحتفظ QGIS Server بذاكرة تخزين مؤقت للقدرات (capabilities)، لذا للحصول على ذاكرة تخزين مؤقت لكل دور يمكنك إعادة الدور في هذه الدالة. أو أعِد None لتعطيل التخزين المؤقت تمامًا.
20.4.2. الخدمات المخصصة¶
في QGIS Server، تُنفَّذ الخدمات الأساسية مثل WMS وWFS وWCS كأصناف فرعية من QgsService.
لتنفيذ خدمة جديدة تُنفَّذ عندما يطابق معامل سلسلة الاستعلام SERVICE اسم الخدمة، يمكنك تنفيذ QgsService خاص بك وتسجيل خدمتك في serviceRegistry() باستدعاء الدالة registerService(service) الخاصة به.
إليك مثالًا على خدمة مخصصة باسم CUSTOM:
1from qgis.server import QgsService
2from qgis.core import QgsMessageLog
3
4class CustomServiceService(QgsService):
5
6 def __init__(self):
7 QgsService.__init__(self)
8
9 def name(self):
10 return "CUSTOM"
11
12 def version(self):
13 return "1.0.0"
14
15 def executeRequest(self, request, response, project):
16 response.setStatusCode(200)
17 QgsMessageLog.logMessage('Custom service executeRequest')
18 response.write("Custom service executeRequest")
19
20
21class CustomService():
22
23 def __init__(self, serverIface):
24 serverIface.serviceRegistry().registerService(CustomServiceService())
20.4.3. واجهات API المخصصة¶
في QGIS Server، تُنفَّذ واجهات OGC API الأساسية مثل OAPIF (المعروفة أيضًا باسم WFS3) كمجموعات من الأصناف الفرعية لـ QgsServerOgcApiHandler المسجلة في نسخة من QgsServerOgcApi (أو صنفها الأب QgsServerApi).
لتنفيذ واجهة API جديدة تُنفَّذ عندما يطابق مسار URL عنوانًا معينًا، يمكنك تنفيذ نسخ QgsServerOgcApiHandler خاصة بك، وإضافتها إلى QgsServerOgcApi، ثم تسجيل الواجهة في serviceRegistry() باستدعاء الدالة registerApi(api) الخاصة به.
إليك مثالًا على واجهة API مخصصة تُنفَّذ عندما يحتوي عنوان URL على /customapi:
1import json
2import os
3
4from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream, QRegularExpression
5from qgis.server import (
6 QgsServiceRegistry,
7 QgsService,
8 QgsServerFilter,
9 QgsServerOgcApi,
10 QgsServerQueryStringParameter,
11 QgsServerOgcApiHandler,
12)
13
14from qgis.core import (
15 QgsMessageLog,
16 QgsJsonExporter,
17 QgsCircle,
18 QgsFeature,
19 QgsPoint,
20 QgsGeometry,
21)
22
23
24class CustomApiHandler(QgsServerOgcApiHandler):
25
26 def __init__(self):
27 super(CustomApiHandler, self).__init__()
28 self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
29
30 def path(self):
31 return QRegularExpression("/customapi")
32
33 def operationId(self):
34 return "CustomApiXYCircle"
35
36 def summary(self):
37 return "Creates a circle around a point"
38
39 def description(self):
40 return "Creates a circle around a point"
41
42 def linkTitle(self):
43 return "Custom Api XY Circle"
44
45 def linkType(self):
46 return QgsServerOgcApi.data
47
48 def handleRequest(self, context):
49 """Simple Circle"""
50
51 values = self.values(context)
52 x = values['x']
53 y = values['y']
54 r = values['r']
55 f = QgsFeature()
56 f.setAttributes([x, y, r])
57 f.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString())
58 exporter = QgsJsonExporter()
59 self.write(json.loads(exporter.exportFeature(f)), context)
60
61 def templatePath(self, context):
62 # The template path is used to serve HTML content
63 return os.path.join(os.path.dirname(__file__), 'circle.html')
64
65 def parameters(self, context):
66 return [QgsServerQueryStringParameter('x', True, QgsServerQueryStringParameter.Type.Double, 'X coordinate'),
67 QgsServerQueryStringParameter(
68 'y', True, QgsServerQueryStringParameter.Type.Double, 'Y coordinate'),
69 QgsServerQueryStringParameter('r', True, QgsServerQueryStringParameter.Type.Double, 'radius')]
70
71
72class CustomApi():
73
74 def __init__(self, serverIface):
75 api = QgsServerOgcApi(serverIface, '/customapi',
76 'custom api', 'a custom api', '1.1')
77 handler = CustomApiHandler()
78 api.registerHandler(handler)
79 serverIface.serviceRegistry().registerApi(api)