Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
119b892
added BaseFilterBackend which DjangoFilterBackend subclasses
miki725 Sep 6, 2015
bb4df38
added SQLAlchemyFilterBackend with support for most lookups
miki725 Sep 7, 2015
1923e70
added SQASessionMiddleware which adds request.sqa_session
miki725 Sep 7, 2015
2a6698a
added SQLAlchemy models for one_to_one test app
miki725 Sep 7, 2015
f22fd77
initial implementation of SQLAlchemyModelFilterSet
miki725 Sep 7, 2015
4d3e94b
added sqlalchemy as dependency
miki725 Sep 7, 2015
6c68fe8
support for related fields in SQLAlchemyModelFilterSet
miki725 Sep 8, 2015
edc8841
mapping Integer field
miki725 Sep 10, 2015
967f498
moved sqlalchemy utilities to SQA backend and added relation filter s…
miki725 Sep 12, 2015
4e1a7cb
not filtering when no specs are available in SQA
miki725 Sep 12, 2015
2e8c0df
mapping Date columns in SQLAlchemy
miki725 Sep 12, 2015
457b542
added example APIs for all SQLAlchemy models
miki725 Sep 12, 2015
323f56f
fixes #7. implements __repr__ for FilterSets
miki725 Sep 12, 2015
386f481
added alchemy_db pytest fixture
miki725 Sep 12, 2015
16b20b7
added tests for SQLAlchemy support
miki725 Sep 12, 2015
b7b9b84
renamed SQA to SQLAlchemy throughout code and tests
miki725 Sep 12, 2015
2d63e52
updated apidocs [ci skip]
miki725 Sep 12, 2015
5ebe04b
adjusted doc to mention that SQLAlchemy is supported
miki725 Sep 12, 2015
3364007
bumped version to 0.2.0 and added details to history [ci skip]
miki725 Sep 12, 2015
678a42c
added SQLAlchemy usage example in docs [ci skip]
miki725 Sep 12, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
History
-------

0.2.0 (2015-09-12)
~~~~~~~~~~~~~~~~~~

* Added `SQLAlchemy <http://www.sqlalchemy.org/>`_ support.
* ``FilterSet`` instances have much more useful ``__repr__`` which
shows all filters at a glance. For example::

>>> PlaceFilterSet()
PlaceFilterSet()
address = Filter(form_field=CharField, lookups=ALL, default_lookup="exact", is_default=False)
id = Filter(form_field=IntegerField, lookups=ALL, default_lookup="exact", is_default=True)
name = Filter(form_field=CharField, lookups=ALL, default_lookup="exact", is_default=False)
restaurant = RestaurantFilterSet()
serves_hot_dogs = Filter(form_field=BooleanField, lookups=ALL, default_lookup="exact", is_default=False)
serves_pizza = Filter(form_field=BooleanField, lookups=ALL, default_lookup="exact", is_default=False)
waiter = WaiterFilterSet()
id = Filter(form_field=IntegerField, lookups=ALL, default_lookup="exact", is_default=True)
name = Filter(form_field=CharField, lookups=ALL, default_lookup="exact", is_default=False)

0.1.1 (2015-09-06)
~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 7 additions & 0 deletions docs/api/url_filter.backends.base.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
url_filter.backends.base module
===============================

.. automodule:: url_filter.backends.base
:members:
:undoc-members:
:show-inheritance:
2 changes: 2 additions & 0 deletions docs/api/url_filter.backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ Submodules

.. toctree::

url_filter.backends.base
url_filter.backends.django
url_filter.backends.sqlalchemy

7 changes: 7 additions & 0 deletions docs/api/url_filter.backends.sqlalchemy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
url_filter.backends.sqlalchemy module
=====================================

.. automodule:: url_filter.backends.sqlalchemy
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/api/url_filter.filtersets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Submodules

url_filter.filtersets.base
url_filter.filtersets.django
url_filter.filtersets.sqlalchemy

7 changes: 7 additions & 0 deletions docs/api/url_filter.filtersets.sqlalchemy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
url_filter.filtersets.sqlalchemy module
=======================================

.. automodule:: url_filter.filtersets.sqlalchemy
:members:
:undoc-members:
:show-inheritance:
12 changes: 6 additions & 6 deletions docs/big_picture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,16 @@ Filtering
+++++++++

Since filtering is decoupled from the ``FilterSet``, the filtering honors
all full on a specified filter backend. The backend is very simple.
all go to a specified filter backend. The backend is very simple.
It takes a list of filter specifications and a data to filter and its
job is to filter that data as specified in the specifications.

.. note::
Currently we only support Django ORM filter backend but you can imagine
that any backend can be implemented. We plan to add support for SQLAlchemy
since, well, why not add it? Eventually filter backends can be added
for flat data-structures like filtering a vanilla Python lists or
filtering from exotic data-source like Mongo.
Currently we only support Django ORM and SQLAlchemy filter backends
but you can imagine that any backend can be implemented.
Eventually filter backends can be added for flat data-structures
like filtering a vanilla Python lists or even more exotic sources
like Mongo, Redis, etc.

Steps
-----
Expand Down
1 change: 1 addition & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.. include:: ../HISTORY.rst
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Contents

usage
big_picture
history
api/modules

.. include:: ../README.rst
Expand Down
29 changes: 29 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,35 @@ Notable things:
model = User
fields = ['username', 'email', 'joined', 'profile']

SQLAlchemy
----------

`SQLAlchemy <http://www.sqlalchemy.org/>`_ works very similar to how Django
backend works. For example::

from django import forms
from url_filter.backend.sqlalchemy import SQLAlchemyFilterBackend
from url_filter.filtersets.sqlalchemy import SQLAlchemyModelFilterSet

class UserFilterSet(SQLAlchemyModelFilterSet):
filter_backend_class = SQLAlchemyFilterBackend

class Meta(object):
model = User # this model should be SQLAlchemy model
fields = ['username', 'email', 'joined', 'profile']

fs = UserFilterSet(data=QueryDict(), queryset=session.query(User))
fs.filter()

Notable things:

* this works exactly same as ``ModelFitlerSet`` so refer above for some of
general options.
* ``filter_backend_class`` **must** be provided since otherwise
``DjangoFilterBackend`` will be used which will obviously not work
with SQLAlchemy models.
* ``queryset`` given to the queryset should be SQLAlchemy query object.

Integrations
------------

Expand Down
5 changes: 3 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
-r requirements.txt
Sphinx
Werkzeug
coverage
django-extensions
djangorestframework
Expand All @@ -10,7 +8,10 @@ mock
pytest
pytest-cov
pytest-django
sphinx
sphinx-autobuild
sphinx-rtd-theme
sqlalchemy
tox
watchdog
werkzeug
7 changes: 7 additions & 0 deletions test_project/alchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()
44 changes: 44 additions & 0 deletions test_project/many_to_many/alchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import backref, relationship
from sqlalchemy.schema import ForeignKey, Table

from ..alchemy import Base


class Publication(Base):
__tablename__ = 'many_to_many_publication'
id = Column(Integer, primary_key=True)
title = Column(String(30))

@property
def pk(self):
return self.id


publication_article_association_table = Table(
'many_to_many_article_publications',
Base.metadata,
Column('id', Integer),
Column('publication_id', Integer, ForeignKey('many_to_many_publication.id')),
Column('article_id', Integer, ForeignKey('many_to_many_article.id')),
)


class Article(Base):
__tablename__ = 'many_to_many_article'
id = Column(Integer, primary_key=True)
headline = Column(String(100))

publications = relationship(
Publication,
secondary=publication_article_association_table,
backref=backref('articles', uselist=True),
uselist=True,
)

@property
def pk(self):
return self.id
45 changes: 39 additions & 6 deletions test_project/many_to_many/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
from __future__ import print_function, unicode_literals

from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ReadOnlyModelViewSet

from url_filter.backends.sqlalchemy import SQLAlchemyFilterBackend
from url_filter.filtersets import ModelFilterSet
from url_filter.filtersets.sqlalchemy import SQLAlchemyModelFilterSet

from . import alchemy
from .models import Article, Publication


Expand Down Expand Up @@ -39,18 +42,48 @@ class Meta(object):
model = Publication


class PublicationViewSet(ModelViewSet):
queryset = Publication.objects.all()
serializer_class = PublicationNestedSerializer
filter_class = PublicationFilterSet
class SQLAlchemyPublicationFilterSet(SQLAlchemyModelFilterSet):
filter_backend_class = SQLAlchemyFilterBackend

class Meta(object):
model = alchemy.Publication


class ArticleFilterSet(ModelFilterSet):
class Meta(object):
model = Article


class ArticleViewSet(ModelViewSet):
class SQLAlchemyArticleFilterSet(SQLAlchemyModelFilterSet):
filter_backend_class = SQLAlchemyFilterBackend

class Meta(object):
model = alchemy.Article


class PublicationViewSet(ReadOnlyModelViewSet):
queryset = Publication.objects.all()
serializer_class = PublicationNestedSerializer
filter_class = PublicationFilterSet


class SQLAlchemyPublicationViewSet(ReadOnlyModelViewSet):
serializer_class = PublicationNestedSerializer
filter_class = SQLAlchemyPublicationFilterSet

def get_queryset(self):
return self.request.alchemy_session.query(alchemy.Publication)


class ArticleViewSet(ReadOnlyModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleNestedSerializer
filter_class = ArticleFilterSet


class SQLAlchemyArticleViewSet(ReadOnlyModelViewSet):
serializer_class = ArticleNestedSerializer
filter_class = SQLAlchemyArticleFilterSet

def get_queryset(self):
return self.request.alchemy_session.query(alchemy.Article)
39 changes: 39 additions & 0 deletions test_project/many_to_one/alchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

from sqlalchemy import Column, Date, Integer, String
from sqlalchemy.orm import backref, relationship

from ..alchemy import Base


class Reporter(Base):
__tablename__ = 'many_to_one_reporter'
id = Column(Integer, primary_key=True)
first_name = Column(String(30))
last_name = Column(String(30))
email = Column(String(254))

@property
def pk(self):
return self.id


class Article(Base):
__tablename__ = 'many_to_one_article'
id = Column(Integer, primary_key=True)
reporter_id = Column(Integer)
headline = Column(String(100))
pub_date = Column(Date)

reporter = relationship(
Reporter,
backref=backref('articles', uselist=True),
uselist=False,
primaryjoin='test_project.many_to_one.alchemy.Article.reporter_id == Reporter.id',
foreign_keys=reporter_id,
)

@property
def pk(self):
return self.id
39 changes: 36 additions & 3 deletions test_project/many_to_one/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
from __future__ import print_function, unicode_literals

from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.viewsets import ReadOnlyModelViewSet

from url_filter.backends.sqlalchemy import SQLAlchemyFilterBackend
from url_filter.filtersets import ModelFilterSet
from url_filter.filtersets.sqlalchemy import SQLAlchemyModelFilterSet

from . import alchemy
from .models import Article, Reporter


Expand Down Expand Up @@ -39,18 +42,48 @@ class Meta(object):
model = Reporter


class SQLAlchemyReporterFilterSet(SQLAlchemyModelFilterSet):
filter_backend_class = SQLAlchemyFilterBackend

class Meta(object):
model = alchemy.Reporter


class ArticleFilterSet(ModelFilterSet):
class Meta(object):
model = Article


class ReporterViewSet(ModelViewSet):
class SQLAlchemyArticleFilterSet(SQLAlchemyModelFilterSet):
filter_backend_class = SQLAlchemyFilterBackend

class Meta(object):
model = alchemy.Article


class ReporterViewSet(ReadOnlyModelViewSet):
queryset = Reporter.objects.all()
serializer_class = ReporterNestedSerializer
filter_class = ReporterFilterSet


class ArticleViewSet(ModelViewSet):
class SQLAlchemyReporterViewSet(ReadOnlyModelViewSet):
serializer_class = ReporterNestedSerializer
filter_class = SQLAlchemyReporterFilterSet

def get_queryset(self):
return self.request.alchemy_session.query(alchemy.Reporter)


class ArticleViewSet(ReadOnlyModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleNestedSerializer
filter_class = ArticleFilterSet


class SQLAlchemyArticleViewSet(ReadOnlyModelViewSet):
serializer_class = ArticleNestedSerializer
filter_class = SQLAlchemyArticleFilterSet

def get_queryset(self):
return self.request.alchemy_session.query(alchemy.Article)
17 changes: 17 additions & 0 deletions test_project/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals

from django.conf import settings
from sqlalchemy.orm import sessionmaker


Session = sessionmaker(bind=settings.SQLALCHEMY_ENGINE)


class SQLAlchemySessionMiddleware(object):
def process_request(self, request):
request.alchemy_session = Session()

def process_response(self, request, response):
request.alchemy_session.close()
return response
Loading