Skip to content

Commit 962ecf4

Browse files
Initial commit
0 parents  commit 962ecf4

File tree

13 files changed

+320
-0
lines changed

13 files changed

+320
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/.tox

LICENSE.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright (c) 2017 Greg Brown
2+
[gregbrown.co.nz](http://gregbrown.co.nz)
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions
7+
are met:
8+
1. Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
3. The name of the author may not be used to endorse or promote products
14+
derived from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include LICENSE.txt
2+
include README.md

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Django utility to retrieve the next or previous object, given a current object
2+
and a queryset.
3+
4+
See <http://gregbrown.co.nz/code/django-next-prev/> for more details.
5+
6+
[![Circle CI](https://circleci.com/gh/gregplaysguitar/django-next-prev.svg?style=svg)](https://circleci.com/gh/gregplaysguitar/django-next-prev)
7+
[![codecov](https://codecov.io/gh/gregplaysguitar/django-next-prev/branch/master/graph/badge.svg)](https://codecov.io/gh/gregplaysguitar/django-next-prev)
8+
[![Latest Version](https://img.shields.io/pypi/v/django-next-prev.svg?style=flat)](https://pypi.python.org/pypi/django-next-prev/)
9+
10+
11+
## Installation
12+
13+
Download the source from https://pypi.python.org/pypi/django-next-prev/
14+
and run `python setup.py install`, or:
15+
16+
> pip install django-next-prev
17+
18+
Django 1.8 or higher is required.
19+
20+
21+
## Quick start

circle.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
dependencies:
2+
override:
3+
- pip install tox tox-pyenv
4+
- pyenv local 2.7.12 3.4.4 3.5.2
5+
post:
6+
- pip install coverage
7+
- python ./setup.py install
8+
test:
9+
post:
10+
- coverage run --source next_prev ./runtests.py
11+
- bash <(curl -s https://codecov.io/bash)

next_prev.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from functools import partial
4+
5+
from django.db import models
6+
7+
if not locals().get('reduce'):
8+
from functools import reduce
9+
10+
__version__ = '1.0.0'
11+
VERSION = tuple(map(int, __version__.split('.')))
12+
13+
14+
def get_model_attr(instance, attr):
15+
"""Example usage: get_model_attr(instance, 'category__slug')"""
16+
for field in attr.split('__'):
17+
instance = getattr(instance, field)
18+
return instance
19+
20+
21+
def next_or_prev_in_order(instance, qs=None, prev=False, loop=False):
22+
"""Get the next (or previous with prev=True) item for instance, from the
23+
given queryset (which is assumed to contain instance) respecting
24+
queryset ordering. If loop is True, return the first/last item when the
25+
end/start is reached. """
26+
27+
if not qs:
28+
qs = instance.__class__.objects.all()
29+
30+
if prev:
31+
qs = qs.reverse()
32+
lookup = 'lt'
33+
else:
34+
lookup = 'gt'
35+
36+
q_list = []
37+
prev_fields = []
38+
39+
if qs.query.extra_order_by:
40+
ordering = qs.query.extra_order_by
41+
elif not qs.query.default_ordering:
42+
ordering = qs.query.order_by
43+
else:
44+
ordering = qs.query.order_by or qs.query.get_meta().ordering
45+
46+
ordering = list(ordering)
47+
48+
for field in (ordering + ['pk']):
49+
if field[0] == '-':
50+
this_lookup = (lookup == 'gt' and 'lt' or 'gt')
51+
field = field[1:]
52+
else:
53+
this_lookup = lookup
54+
q_kwargs = dict([(f, get_model_attr(instance, f))
55+
for f in prev_fields])
56+
key = "%s__%s" % (field, this_lookup)
57+
q_kwargs[key] = get_model_attr(instance, field)
58+
q_list.append(models.Q(**q_kwargs))
59+
prev_fields.append(field)
60+
try:
61+
return qs.filter(reduce(models.Q.__or__, q_list))[0]
62+
except IndexError:
63+
length = qs.count()
64+
if loop and length > 1:
65+
# queryset is reversed above if prev
66+
return qs[0]
67+
return None
68+
69+
70+
next_in_order = partial(next_or_prev_in_order, prev=False)
71+
prev_in_order = partial(next_or_prev_in_order, prev=True)

runtests.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
import os
3+
import sys
4+
5+
import django
6+
from django.conf import settings
7+
from django.test.utils import get_runner
8+
9+
10+
if __name__ == "__main__":
11+
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
12+
django.setup()
13+
TestRunner = get_runner(settings)
14+
test_runner = TestRunner()
15+
failures = test_runner.run_tests(["tests"])
16+
17+
# Return failures
18+
sys.exit(bool(failures))

setup.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python
2+
# coding: utf8
3+
4+
import os
5+
6+
from setuptools import setup
7+
8+
# if there's a converted readme, use it, otherwise fall back to markdown
9+
if os.path.exists('README.rst'):
10+
readme_path = 'README.rst'
11+
else:
12+
readme_path = 'README.md'
13+
14+
setup(
15+
name='django-next-prev',
16+
version='1.0.0',
17+
description='Configurable search for django models',
18+
long_description=open(readme_path).read(),
19+
author='Greg Brown',
20+
author_email='[email protected]',
21+
url='https://github.com/gregplaysguitar/django-next-prev',
22+
packages=[],
23+
license='BSD License',
24+
zip_safe=False,
25+
platforms='any',
26+
install_requires=['Django>=1.8'],
27+
include_package_data=True,
28+
package_data={},
29+
py_modules=['next_prev'],
30+
classifiers=[
31+
'Development Status :: 5 - Production/Stable',
32+
'Environment :: Web Environment',
33+
'Intended Audience :: Developers',
34+
'Operating System :: OS Independent',
35+
'Programming Language :: Python',
36+
'Programming Language :: Python :: 2',
37+
'Programming Language :: Python :: 2.7',
38+
'Programming Language :: Python :: 3',
39+
'Programming Language :: Python :: 3.4',
40+
'Programming Language :: Python :: 3.5',
41+
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
42+
'Framework :: Django',
43+
],
44+
)

tests/__init__.py

Whitespace-only changes.

tests/models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.db import models
2+
from django.utils.encoding import python_2_unicode_compatible
3+
4+
5+
@python_2_unicode_compatible
6+
class Category(models.Model):
7+
title = models.CharField(max_length=100)
8+
9+
def __str__(self):
10+
return self.title
11+
12+
13+
@python_2_unicode_compatible
14+
class Post(models.Model):
15+
title = models.CharField(max_length=100)
16+
category = models.ForeignKey(Category, on_delete=models.CASCADE)
17+
created = models.DateField()
18+
text = models.TextField()
19+
20+
def __str__(self):
21+
return self.title
22+
23+
class Meta:
24+
ordering = ('created', 'title')

0 commit comments

Comments
 (0)