Django-filter: Filter untuk contrib.postgres JSONField

Dibuat pada 4 Jun 2016  ·  16Komentar  ·  Sumber: carltongibson/django-filter

Ini dimulai di grup diskusi Google:
https://groups.google.com/forum/#!topic/django -filter/RwNfoWsdeLQ

Saya tertarik untuk dapat memfilter contrib.postgres JSONFields dengan Django-filter.

Saya memiliki filter yang berfungsi untuk beberapa contoh. Ini lebih rumit daripada yang saya kira karena Anda tidak benar-benar mengetahui tipe data di JSON sebelumnya seperti yang Anda lakukan dengan sesuatu seperti IntegerField. Saya mungkin hanya membuatnya terlalu rumit.

Berikut adalah contoh filter yang mengenai JSONField saya
http://127.0.0.1 :8000/api/v1/craters?data= latitude:float :-57:lte~!@!~ age:str :PC

Berikut model dan kode filternya:
https://Gist.github.com/jzmiller1/627071f555186cd1a58bb8f065205ff7

Aku akan terus bermain-main dengannya. Jika ada yang memiliki pemikiran atau umpan balik, beri tahu saya ...

Komentar yang paling membantu

Saya pikir akan sangat luar biasa memiliki JSONFilter yang mengaktifkan kueri seperti jsonfield__a_random_key=value . Saya tahu Anda dapat melakukannya dengan metode objects.filter . Mungkin tradeoff bisa menjadi validasi filter?

Semua 16 komentar

Hai @jzmiller1. Apa sebenarnya yang ingin Anda capai? Tidak tahu apakah Anda:

  • mencoba membuat JSONFilter generik yang memungkinkan Anda menanyakan atribut arbitrer apa pun di dalam JSONField. atau,
  • mencoba mengekspos atribut tertentu (lintang, usia) yang umum di kawah Anda data . Atribut ini pada dasarnya akan menjadi skema data Anda.

Yang pertama menarik, tetapi seperti yang Anda ketahui, komplikasinya terletak pada JSONField secara inheren tanpa skema. Tanpa skema, Anda tidak dapat menulis kode untuk membuat filter secara otomatis. MethodFilter Anda berfungsi karena memungkinkan pencarian atribut arbitrer apa pun, tetapi Anda tidak dapat memvalidasi pencarian tersebut. misalnya, ?data=latitude:char:PC:isnull dimungkinkan, tetapi tidak masuk akal. Solusi apa pun di sini akan membutuhkan kompromi. Filter yang sepenuhnya arbitrer tidak akan dapat memvalidasi pencarian, filter yang memvalidasi akan memerlukan beberapa cara untuk menyediakan skema.

Untuk kasus kedua, solusinya bertele-tele/membosankan, tetapi langsung.

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')
    ...

Kueri Anda kemudian akan terlihat seperti:

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

Tujuan saya adalah membuat JSONFilter generik yang akan memungkinkan kueri pada atribut arbitrer apa pun di dalam JSONField. Untuk apa yang saya kerjakan, saya tidak akan benar-benar tahu apa yang ada di dalam data untuk kawah tertentu, tetapi jika ada kunci di sana yang saya cari, saya ingin dapat menanyakannya.

Sejauh ketidakmampuan untuk memvalidasi jenis pencarian, saya pikir saya akan bergantung pada pengguna yang membuat kueri untuk menyadari bahwa kueri itu tidak masuk akal dan tidak membuatnya untuk memulai.

Saya tidak yakin apakah yang saya coba lakukan adalah buang-buang waktu atau tidak. Mungkin ada solusi yang lebih baik di luar sana untuk apa yang saya coba capai. Saya ingin tahu apakah ada yang melihat masalah besar yang akan mencegah hal ini menjadi mungkin atau jika ada yang memiliki kasus penggunaan di mana ini akan berguna. Terima kasih telah melihatnya!

Pikiran pertama saya adalah tentang validasi, sesuai dengan poin @rpkilby . Tanpa skema bagus dari sudut pandang pengembang — tetapi saya tidak yakin Anda ingin menghubungkannya langsung ke URL yang dapat dialamatkan.

Biarkan ini tetap terbuka untuk saat ini. Saya dapat melihatnya sebagai permintaan yang populer. (Jadi bahkan untuk mengatasinya pada tingkat _"Inilah contoh MethodFilter "_ dalam dokumen akan bermanfaat.)

Untuk apa yang saya kerjakan, saya tidak akan benar-benar tahu apa yang ada di dalam data untuk kawah tertentu, tetapi jika ada kunci di sana yang saya cari, saya ingin dapat menanyakannya.

Ini sepertinya... agak funky. Anda menyediakan API untuk data kawah, tetapi Anda tidak tahu apa yang ada dalam data yang Anda berikan? Apakah maksud Anda bahwa beberapa catatan akan kehilangan atribut dari skema umum, atau bahwa catatan individu sepenuhnya arbitrer?

Saya akan menutup ini sebagai Out of Scope untuk saat ini. Senang mempertimbangkan permintaan tarik yang terdokumentasi dan teruji. Kami mungkin memiliki kapasitas untuk mempertimbangkan kembali di masa depan.

Saya pikir akan sangat luar biasa memiliki JSONFilter yang mengaktifkan kueri seperti jsonfield__a_random_key=value . Saya tahu Anda dapat melakukannya dengan metode objects.filter . Mungkin tradeoff bisa menjadi validasi filter?

Baru saja menyelesaikan implementasi kueri 'alami' untuk filter QuerySet menggunakan objek Q. Sudah diuji unit terhadap queryset dengan ~1000 catatan menggunakan JsonField. Pelaksanaannya pada:
https://github.com/shallquist/DJangoQuerySetFilter/blob/master/queryparser.py

Hai @shallquist saya tidak yakin bagaimana menggunakan QuerySetFilter dalam konteks Django-filter. Apakah Anda mendokumentasikan penggunaan di suatu tempat?

Ini cukup mudah digunakan seperti yang ditunjukkan dalam readme di github. Kueri normal harus didukung, mis.
QuerySetFilter('teman').get_Query((person__address__city = Denver | person__address__city = Boulder) & person__address_state ~= CO)
yang akan membuat kueri untuk mengambil semua teman yang tinggal di Dever atau Boulder colorado, di mana teman adalah jsonfield.

BTW Ini belum banyak diuji dan karena filter Django tidak mendukung kueri objek array yang disematkan, saya telah mengabaikan pendekatan ini.

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

Saya pikir akan sangat luar biasa memiliki JSONFilter yang mengaktifkan kueri seperti jsonfield__a_random_key=value. Saya tahu Anda dapat melakukannya dengan metode objects.filter. Mungkin tradeoff bisa menjadi validasi filter?

Hai @carltongibson , @rpkilby Saya ingin mendapatkan pendapat Anda tentang ini. Katakanlah my_field adalah postgres JSONField , dan saya ingin:

  • Tambahkan filter REST dalam bentuk my_field__etc=value di mana etc adalah salah satu kueri yang didukung oleh JSONField dan value apa pun yang disediakan pengguna REST.
  • Lalu saya ingin meneruskan etc dan value ke manajer objek model dalam bentuk MyModel.objects.filter(my_field__etc=value) .
  • Akhirnya ambil apa pun yang dikembalikan filter.

Kelihatannya seperti sangat sepele, tetapi saya belum menemukan cara untuk melakukan hal seperti ini. Jika kalian memberi saya sedikit petunjuk, saya bisa mencoba menerapkannya.

Setiap pemikiran akan sangat dihargai!

Apakah sesuatu seperti berikut ini tidak berfungsi?

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

Secara umum, field_name harus cocok dengan nama bidang model yang mendasarinya, sedangkan transformasi dan pencarian (transformasi kunci dalam kasus ini) harus dimuat dalam lookup_expr .

@rpkilby terima kasih banyak atas tanggapannya yang begitu cepat - Ya persis, tapi saya ingin etc disediakan oleh pengguna dalam permintaan... Jadi saya tidak bisa melakukan hardcode di filter

Filter akan terlihat lebih seperti:

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

Jadi, filter JSON tunggal untuk menangani parameter kueri arbitrer seperti ?my_field__etc=value .

Saya melihat dua masalah. Pertama, bagian dari nilai Django-filter adalah memvalidasi parameter kueri. Karena JSONField s tidak memiliki skema, tidak mungkin membuat filter yang memvalidasi data masuk dengan tepat. misalnya, jika bidang JSON Anda memiliki kunci "hitung", tidak mungkin untuk mengetahui bahwa hanya angka positif yang valid. Yang terbaik yang bisa dilakukan adalah menjamin nilai JSON yang valid. Jadi kueri setidaknya valid, tetapi mungkin tidak masuk akal (mis., data__count__gt='cat' ).

Yang kedua adalah bahwa filter ini akan memiliki batasan yang sama dengan filter berbasis MultiWidget . misalnya, itu tidak akan menghasilkan kesalahan validasi untuk nama param yang benar. Tapi sebelum menyelam ke dalamnya, inilah cara saya mungkin menerapkan filter. Kita butuh:

  • Kelas filter untuk melakukan pemfilteran aktual, yang seharusnya menangani banyak parameter
  • Bidang formulir untuk memvalidasi data JSON
  • Widget untuk mendapatkan data untuk parameter my_field__* arbitrer.
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)

Saya belum menguji hal di atas, tetapi seharusnya kira-kira benar. Namun, ada batasan:

  • Sejauh yang saya tahu, tidak ada cara untuk menangani per-param dengan benar ValidationError s
  • Dukungan skema OpenAPI/CoreAPI yang buruk? Tidak yakin akan seperti apa ini.
  • djangorestframework-filters tidak kompatibel dengan MultiWidget . Filter/widget ini akan mengalami masalah yang sama karena alasan yang sama.

@rpkilby terima kasih banyak atas tanggapan menyeluruh ini.

jika bidang JSON Anda memiliki kunci "hitung", tidak mungkin untuk mengetahui bahwa hanya angka positif yang valid.

Ini adalah poin yang bagus, fakta bahwa kami tidak dapat memvalidasi jenis nilai kueri membuatnya sangat menantang karena MyModel.objects.filter(data__count="1") tidak akan mengembalikan sama dengan MyModel.objects.filter(data__count=1) . Seperti yang Anda katakan, tidak ada cara untuk menebak tipe nilai dari parameter kueri.

Karenanya hanya menyisakan opsi untuk menyematkan info jenis dalam nilai kueri, melakukan sesuatu seperti ?data__count=1:int untuk mencari bilangan bulat dan ?data__count=1:str untuk string dan seterusnya. Tetapi seperti yang disarankan di sini , ini tidak disarankan.

Sekarang saya mengerti mengapa sangat berharga untuk mendefinisikan filter secara eksplisit. Namun demikian, saya akan mencobanya untuk saran Anda! Terima kasih lagi

@rpkilby , saya memiliki kebutuhan yang sama.

Saya memiliki tabel konfigurasi seperti ini dengan dua kolom

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

Saya memiliki tabel lain bernama config_data yang akan memiliki 3 kolom.

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

Catatan: Tabel yang disebutkan di atas bukanlah tabel yang sebenarnya. Mereka hanya versi perwakilan untuk menyampaikan pesan.

Saat ini saya memvalidasi bidang dalam tabel meta_info sebelum menyimpan dengan memeriksa kecocokannya dari tabel konfigurasi.

Kebutuhannya adalah saya ingin memfilter menggunakan kolom meta_info dari tabel config_data. Misalnya. meta_info__key1='abc'. (key1 bisa apa saja)

Saya mencoba menggunakan pendekatan yang telah Anda berikan di atas tetapi masalahnya adalah bagaimana cara menggunakan kelas JSONFilter yang telah Anda buat di atas.

Misalnya.

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

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

Sekarang, Jika saya menjalankan pp.qs atau pp.filter_queryset() itu tidak akan benar-benar menerapkan filter pada bidang meta_info karena nama bidang yang ditetapkan di kelas ConfigDataFilterSet adalah meta_info. Bisakah Anda membantu saya mengatasi rintangan ini?

Apakah halaman ini membantu?
0 / 5 - 0 peringkat