taigaio/taiga-back — pull request #41 by ikame (2014-05-22 16:36:20+00:00)
Title: Moving neighbors logic into standalone module

Moving neighbors logic into standalone module `taiga.base.neighbors`.

### Usage ###

Get the neighbors of a model instance (obj):
```python
from taiga.base.neighbors import get_neighbors

obj = MyModel.objects.get(id=1)

neighbors = get_neighbors(obj)
```

Get the neighbors of a model instance from a restricted results set:
```python
from taiga.base.neighbors import get_neighbors

obj = MyModel.objects.get(id=1)
public_objects = MyModel.objects.filter(public=True)

neighbors = get_neighbors(obj, results_set=public_objects)
```

### Design Explanation ###

In order to get the neighbors of an object we need to:

1. Find the object at the **left**, that is, the **last object** of the set of
objects positioned **before** the main object.
2. Find the object at the **right**, that is, the **first object** out of the
set of objects positioned **after** the main object.

Since the objects may be sorted in any order we cannot use a single
field name to do the filtering and we need to filter depending on the
ordering fields. What the `get_neighbors` function does then is to
combine the ordering criteria of the `results_set` param with the
ordering criteria of obj's model class and create the necessary
filters with them.

Now, sometimes this is not enough and you need to use certain specific
filters depending on which field is used for sorting. For those cases
the following strategy is used:

If `MyModel` implements the method
`get_neighbors_additional_filters(results_set, ordering, reverse)`
then that method is called in order to get the necessary filters to
find the objects that are before/after (aka neighbors) the current
object. The result of this method should be `None` if you want to
fallback to the default filters or a list of dicts where the dict
content represent conditions that are combined with _AND_ and those
conditions are combined with _OR_ for different dicts. For example:
```python
[{"first_name": "Chuck", "last_name": "Norris"}, {"age__lte": 0}]
```
The above condition is equivalent to:
```sql
WHERE (first_name = 'Chuck' AND last_name = 'Norris') OR (age <= 0) ...
```

With that in mind, you can refine the filters you need to find the
neighbors:
```python
from taiga.base.neighbors import get_neighbors

class MyModel:
def get_neighbors_additional_filters(self, results_set, ordering, reverse):
return [{"fieldname__lte": self.field}, ...]

obj = MyModel.objects.get(id=1)
public_objects = MyModel.objects.filter(public=True)

neighbors = get_neighbors(obj, results_set=public_objects)
```

### Slow Tests ###

The tests for this feature require database access and therefore and
kind of slow, because of that when running the test suit these tests
are skipped unless `--runslow` option is given.