Django-filter: تصفية لـ Contrib.postgres JSONField

تم إنشاؤها على ٤ يونيو ٢٠١٦  ·  16تعليقات  ·  مصدر: carltongibson/django-filter

بدأ هذا في مجموعة مناقشة Google:
https://groups.google.com/forum/#!topic/django -filter / RwNfoWsdeLQ

أنا مهتم بأن أكون قادرًا على تصفية Contrib.postgres JSONFields باستخدام django-filter.

لدي مرشح يعمل لبعض الأمثلة. هذا الأمر أكثر تعقيدًا مما اعتقدت في أنك لا تعرف حقًا نوع البيانات في JSON الخاص بك في وقت مبكر بالطريقة التي تفعل بها شيئًا مثل IntegerField. قد أجعل الأمر معقدًا للغاية.

فيما يلي مثال على الفلتر الذي يصل إلى JSONField
http://127.0.0.1 : 8000 / api / v1 / craters؟ data = latitude: float : -57: lte ~! @! ~ age: str : PC

ها هي النماذج ورمز الفلتر:
https://gist.github.com/jzmiller1/627071f555186cd1a58bb8f065205ff7

سأستمر في العبث بها. إذا كان لدى أي شخص أي أفكار أو ملاحظات ، فيرجى إبلاغي بذلك ...

التعليق الأكثر فائدة

أعتقد أنه سيكون من المدهش حقًا أن يكون لديك JSONFilter لتمكين استعلامات مثل jsonfield__a_random_key=value . أعلم أنه يمكنك القيام بذلك باستخدام طريقة objects.filter . ربما تكون المقايضة هي التحقق من صحة التصفية؟

ال 16 كومينتر

مرحبًا @ jzmiller1. ماذا تحاول انجازه بالضبط؟ لا أستطيع معرفة ما إذا كنت:

  • محاولة إنشاء JSONFilter عام يسمح لك بالاستعلام عن أي سمة عشوائية داخل JSONField. أو،
  • محاولة الكشف عن سمات معينة (خط العرض ، العمر) المشتركة في فوهة البركان الخاصة بك data . ستكون هذه السمات بشكل أساسي مخطط data الخاص بك.

الأول مثير للاهتمام ، ولكن كما اكتشفت أن التعقيد يكمن في أن JSONField بطبيعته غير مخطط. بدون مخطط ، لا يمكنك كتابة رمز لإنشاء عوامل تصفية تلقائيًا. يعمل MethodFilter الخاص بك من حيث أنه يسمح بأي بحث عشوائي عن السمات ، ولكن لا يمكنك التحقق من صحة عمليات البحث هذه. على سبيل المثال ، ?data=latitude:char:PC:isnull ممكن ، لكن لا معنى له. مهما كان الحل هنا سوف يتطلب مقايضة. لن يتمكن عامل التصفية العشوائي تمامًا من التحقق من صحة عمليات البحث ، وسيتطلب عامل التصفية التحقق من الصحة طريقة ما لتوفير مخطط قاعدة بيانات.

بالنسبة للحالة الثانية ، تكون الحلول مطولة / مملة ، ولكنها مباشرة.

class CratersFilter(filters.FilterSet):
    latitude = filters.NumberFilter(name='data__latitude', lookup_expr='exact')
    latitude__lt = filters.NumberFilter(name='data__latitude', lookup_expr='lt')
    latitude__gt = filters.NumberFilter(name='data__latitude', lookup_expr='gt')
    latitude__isnull = filters.BooleanFilter(name='data__latitude', lookup_expr='isnull')
    # not sure if 'isnull' is a valid lookup for JSONFields - just demonstrating that 
    # different lookups expect different value types.

    age = filters.CharFilter(name='data__age', lookup_expr='exact')
    ...

سيبدو استعلامك بعد ذلك كما يلي:

http://127.0.0.1:8000/api/v1/craters?latitude__lte=-57&age=PC

هدفي هو إنشاء JSONFilter عام يسمح بالاستعلامات عن أي سمة عشوائية داخل JSONField. بالنسبة لما أعمل عليه ، لن أعرف حقًا ما يوجد داخل البيانات الخاصة بحفرة معينة ، ولكن إذا كان هناك مفتاح هناك أبحث عنه ، فأنا أود أن أتمكن من الاستعلام عنه.

بقدر ما تذهب عدم القدرة على التحقق من صحة أنواع البحث ، أعتقد أنني سأعتمد على المستخدم الذي يقوم بالاستعلام ليدرك أن الاستعلام غير منطقي ولا يبدأ به.

لست متأكدًا مما إذا كان ما أحاول فعله هو مضيعة للوقت أم لا. قد يكون هناك حل أفضل لما أحاول تحقيقه. كنت أشعر بالفضول إذا رأى أي شخص مشكلات كبيرة من شأنها أن تمنع هذا من أن يكون ممكنًا أو إذا كان لدى أي شخص حالة استخدام حيث يكون ذلك مفيدًا. شكرا لإلقاء نظرة على ذلك!

فكرتي الأولى حول التحقق ، وفقًا لنقطةrpkilby . يعد Schema-less أمرًا رائعًا من وجهة نظر المطور - لكنني لست متأكدًا من أنك تريد توصيله مباشرة بعناوين URL القابلة للتوجيه.

دعونا نبقي هذا مفتوحا الآن. أستطيع أن أرى أنه طلب شائع. (لذا حتى معالجتها على مستوى _ "هذا مثال MethodFilter " _ في المستندات سيكون مفيدًا.)

بالنسبة لما أعمل عليه ، لن أعرف حقًا ما يوجد داخل البيانات الخاصة بحفرة معينة ، ولكن إذا كان هناك مفتاح هناك أبحث عنه ، فأنا أود أن أتمكن من الاستعلام عنه.

هذا يبدو ... نوع من غير تقليدي. أنت تقدم واجهة برمجة تطبيقات لبيانات الحفرة ، لكنك لا تعرف ما هو موجود في البيانات التي تقدمها؟ هل تقصد أن بعض السجلات ستفتقد إلى سمات مخطط شائع ، أو أن السجلات الفردية تعسفية تمامًا؟

سأغلق هذا على أنه خارج النطاق في الوقت الحالي. يسعدني النظر في طلبات السحب الموثقة والمختبرة. قد تكون لدينا القدرة على إعادة النظر في المستقبل.

أعتقد أنه سيكون من المدهش حقًا أن يكون لديك JSONFilter لتمكين استعلامات مثل jsonfield__a_random_key=value . أعلم أنه يمكنك القيام بذلك باستخدام طريقة objects.filter . ربما تكون المقايضة هي التحقق من صحة التصفية؟

أكملت للتو تنفيذ الاستعلام "الطبيعي" لمرشح QuerySet باستخدام كائن Q. لقد تم اختبار الوحدة مقابل مجموعة استعلامات مع ما يقرب من 1000 سجل باستخدام JsonField. التنفيذ في:
https://github.com/shallquist/DJangoQuerySetFilter/blob/master/queryparser.py

مرحبًا shallquist ، لست متأكدًا من كيفية استخدام QuerySetFilter في سياق مرشح django. هل قمت بتوثيق الاستخدام في مكان ما؟

من السهل جدًا استخدامه كما هو موضح في الملف التمهيدي على github. يجب دعم الاستعلامات العادية ، مثل.
QuerySetFilter ("أصدقاء"). get_Query ((person__address__city = Denver | person__address__city = Boulder) & person__address_state ~ = CO)
والتي ستنشئ استعلامًا لاسترداد جميع الأصدقاء الذين يعيشون في Dever أو Boulder colorado ، حيث يكون الأصدقاء jsonfield.

راجع للشغل لم يتم اختبار هذا كثيرًا وبما أن مرشحات Django لا تدعم الاستعلام عن كائنات المصفوفات المضمنة ، فقد تخلت عن هذا الأسلوب.

https://github.com/carltongibson/django-filter/issues/426#issuecomment -380224133

أعتقد أنه سيكون من المدهش حقًا أن يكون لديك JSONFilter يتيح استعلامات مثل jsonfield__a_random_key = value. أعلم أنه يمكنك القيام بذلك باستخدام طريقة objects.filter. ربما تكون المقايضة هي التحقق من صحة التصفية؟

مرحبًا carltongibson ، @ rpkilby ، أود الحصول على أفكارك حول هذا الأمر. لنفترض أن my_field هو postgres JSONField ، وأريد:

  • أضف عامل تصفية REST على شكل my_field__etc=value حيث etc هو أي من الاستعلامات التي يدعمها JSONField و value مهما كان ما يقدمه مستخدم REST.
  • ثم أرغب في تمرير etc و value إلى مدير كائنات النموذج في شكل MyModel.objects.filter(my_field__etc=value) .
  • أخيرًا استرجع كل ما يعود المرشح.

يبدو الأمر تافهًا للغاية لكني لم أحسب أن أفعل شيئًا كهذا. إذا أعطوني يا رفاق القليل من الدليل ، فيمكنني محاولة تنفيذه.

أي أفكار ستكون محل تقدير كبير!

هل شيء من هذا القبيل لا يعمل؟

class MyFilter(FilterSet):
    my_field__etc = filters.NumberFilter(field_name='my_field', lookup_expr='etc')

بشكل عام ، يجب أن يتطابق field_name مع اسم حقل النموذج الأساسي ، بينما يجب تضمين عمليات التحويل والبحث (تحويل رئيسي في هذه الحالة) في lookup_expr .

rpkilby شكرًا جزيلاً لمثل هذه الاستجابة السريعة - نعم بالضبط ، لكنني أريد etc إلى بواسطة المستخدم في الطلب ... لذلك لم أستطع فعلاً ترميزه في المرشح

يجب أن يبدو المرشح مثل:

class MyFilter(FilterSet):
    my_field = JSONFieldFilter(field_name='my_field')

لذلك ، مرشح JSON واحد للتعامل مع معلمات الاستعلام العشوائية مثل ?my_field__etc=value .

أرى مشكلتين. أولاً ، جزء من قيمة مرشح django هو أنه يتحقق من صحة معلمات الاستعلام. نظرًا لعدم احتواء JSONField s على مخطط ، لا يمكن إنشاء عوامل تصفية تتحقق بشكل مناسب من صحة البيانات الواردة. على سبيل المثال ، إذا كان حقل JSON الخاص بك يحتوي على مفتاح "حساب" ، فلن يكون من الممكن استنتاج أن الأرقام الموجبة فقط هي الصالحة. أفضل ما يمكن فعله هو ضمان أن القيمة هي JSON صالحة. لذلك ستكون الاستعلامات صحيحة على الأقل ، ولكن من المحتمل ألا تكون منطقية (على سبيل المثال ، data__count__gt='cat' ).

والثاني هو أن هذا الفلتر سيكون له نفس قيود المرشحات المستندة إلى MultiWidget . على سبيل المثال ، لن يولد أخطاء التحقق من صحة أسماء المعلمات الصحيحة. ولكن قبل الخوض في ذلك ، إليكم كيفية تطبيق المرشح على الأرجح. نحن نحتاج:

  • فئة مرشح لإجراء التصفية الفعلية ، والتي يجب أن تتعامل مع معلمات متعددة
  • حقل نموذج للتحقق من صحة بيانات JSON
  • عنصر واجهة مستخدم للحصول على البيانات الخاصة بالمعلمات my_field__* التعسفية.
class JSONWidget(widgets.Textarea):
    """A widget that handles multiple parameters prefixed with the field name."""

    def value_from_datadict(self, data, files, name):
        prefix = f'{name}{LOOKUP_SEP}'

        # this is doing two things: 
        # - matches multiple params for the base field name
        # - in addition to returning the value, we also need the full parameter name
        #   for querying. otherwise, values will be filtered against the base `name`. 
        return {k: v for k, v in data.items() if k.startswith(prefix)}

    def get_context(self, name, value, attrs):
        # to support rendering the widget, you would need to generate subwidgets
        # similar to MultiWidget.get_context.
        pass

class JSONField(postgres.forms.JSONField):
    widget = JSONWidget

    def clean(self, value):
        # note that it's not possible to collect/reraise any validation errors under
        # their actual parameter names. `form.add_error` should be used here, however
        # the field class does not have access to the form instance. raising 
        # ValidationError({k: str(original_exc)}) also does not work. 

        # clean/convert each value
        return {k: super().clean(v) for k, v in value.items()}

class JSONFilter(filters.Filter):
    field_class = JSONField

    def filter(self, qs, value):
        if value in EMPTY_VALUES:
            return qs
        return qs.filter(**value)

لم أختبر ما ورد أعلاه ، لكن يجب أن يكون صحيحًا تقريبًا. ومع ذلك ، هناك قيود:

  • بقدر ما أستطيع أن أقول ، لا توجد طريقة للتعامل بشكل صحيح مع كل معلمة ValidationError s
  • دعم مخطط OpenAPI / CoreAPI ضعيف؟ لست متأكدا كيف سيبدو هذا.
  • مرشحات djangorestframework غير متوافقة مع MultiWidget . قد يواجه هذا المرشح / عنصر واجهة المستخدم نفس المشكلات للأسباب نفسها.

rpkilby شكرًا جزيلاً على هذه الاستجابة الشاملة.

إذا كان حقل JSON الخاص بك يحتوي على مفتاح "حساب" ، فلن يكون من الممكن إدراك أن الأرقام الموجبة فقط هي الصالحة.

هذه نقطة رائعة ، حقيقة أننا لا نستطيع التحقق من صحة نوع قيمة الاستعلام تجعل الأمر صعبًا للغاية لأن MyModel.objects.filter(data__count="1") لن يُرجع مثل MyModel.objects.filter(data__count=1) . كما تقول ، لا توجد طريقة لتخمين نوع القيمة من معامِلات الاستعلام.

ومن ثم ترك خيار تضمين معلومات النوع في قيمة الاستعلام فقط ، والقيام بشيء مثل ?data__count=1:int للبحث عن عدد صحيح و ?data__count=1:str للسلاسل وما إلى ذلك. ولكن كما هو مقترح هنا ، هذا غير مستحسن.

أفهم الآن سبب أهمية تحديد المرشحات بشكل صريح. ومع ذلك ، سأحاول اقتراحك! شكرا لك مرة أخرى

rpkilby ، لديّ حاجة مماثلة.

لدي جدول تكوين مثل هذا مع عمودين

meta_structure of type jsonb (This column has info like key1 of type string, key2 of type integer)

لدي جدول آخر يسمى config_data والذي سيحتوي على 3 أعمدة.

config_id -> Foreign key to config table
meta_info -> jsonb type

ملحوظة: الجداول المذكورة أعلاه ليست بالضبط الجداول. إنها مجرد نسخ تمثيلية لنقل الرسالة.

أقوم حاليًا بالتحقق من صحة الحقول في جدول meta_info قبل الحفظ عن طريق التحقق من تطابقها من جدول التكوين.

الحاجة هي أنني أريد التصفية باستخدام عمود meta_info لجدول config_data. على سبيل المثال. meta_info__key1 = "abc". (يمكن أن يكون key1 أي شيء)

كنت أحاول استخدام النهج الذي قدمته أعلاه ولكن المشكلة هي كيف يمكنني استخدام فئة JSONFilter التي قمت بإنشائها أعلاه.

على سبيل المثال.

class ConfigDataFilterSet(django_filters.FilterSet):
    meta_info = JSONFilter(field_name='meta_info')

pp = ConfigDataFilterSet(data={'meta_info__key1': 'abc'})

الآن ، إذا قمت بتشغيل pp.qs أو pp.filter_queryset() فلن يتم تطبيق عامل التصفية في حقل meta_info لأن اسم الحقل المعين في فئة ConfigDataFilterSet هو meta_info. هل يمكنك مساعدتي للتغلب على هذه العقبة؟

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات