Skip to content

Commit

Permalink
Merge pull request #67 from pstch/develop
Browse files Browse the repository at this point in the history
v9.1 : Global code refactoring, new documentation and documentation tests
  • Loading branch information
Hugo Geoffroy committed Jul 27, 2014
2 parents 841a847 + f65619d commit 0aa90fe
Show file tree
Hide file tree
Showing 88 changed files with 5,043 additions and 1,671 deletions.
19 changes: 11 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ build/
dist/
*.egg-info/

pylint/
pylint.html
.coverage
htmlcov/

.cov2emacs*
.coveralls.yml

pycallgraph.gdf
pycallgraph.png
var/pycallgraph.png

var/docs_html/
var/docs_coverage/
var/docs_doctests/

var/coverage_html/

var/pylint/

docs/_*
/.project
/.pydevproject
example_app/db.sqlite3
19 changes: 8 additions & 11 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
language: python
python:
- "2.7"
- "3.2"
- "3.3"
- "3.4"
env:
- DJANGO=1.6
- DJANGO=1.6 DJANGO_SETTINGS_MODULE=tests.settings
cache:
directories:
- /home/travis/build/cache/
- /home/travis/virtualenv/python3.4.0/lib/python3.4/site-packages
install:
- pip install coveralls
- pip install pep8
- pip install pylint
- pip install -r tests/requirements.txt
- pip install coverage coveralls pep8
- pip install .
script:
- coverage run --source=django_crucrudile runtests.py
- nosetests
after_success:
- pylint --rcfile=.pylintrc django_crucrudile
- pep8 django_crucrudile
- coverage report -m
- coveralls
49 changes: 1 addition & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
django-crucrudile [![Build Status](https://travis-ci.org/pstch/django-crucrudile.svg?branch=crucrudile)](https://travis-ci.org/pstch/django-crucrudile) [![Coverage Status](https://coveralls.io/repos/pstch/django-crucrudile/badge.png?branch=crucrudile)](https://coveralls.io/r/pstch/django-crucrudile?branch=crucrudile)
=================

`django-crucrudile` allows you to create "model mixins", that define possible actions for this model. Those model mixins allow the model to be able to generate its URL patterns by itself, so that they can be included in `urls.py` using just a call to the `get_url_patterns()` method of the model class.

**WARNING :** This project is still a WIP. The implementation of the concept is stable, and I already use it in my own projects, but it will still take some times before I can release a proper version (somewhen in April 2014).

**NOTE :** Development is done on the `develop` branch, and when needed, merged into the `master` branch, which is more stable. **Code on the `develop` branch may not work at all !** (Which doesn't mean that the code on the `master` branch will work..)
Expand All @@ -13,47 +11,7 @@ The documentation, available at [django-crucrudile.readthedocs.org](http://djang

## Example

Here, we create two **model mixins** : `Listable` and `Detailable`, and we set the url_args attribute of DetailView so that django-crucrudile knows that the URL should contain a named capture group for the ID of the object to view.

Then, we create a Django Model, in which we use `Listable` and `Detailable` as mixins.


```python
from django.db.models import Model
from django.generic.views import ListView, DetailView
from django_crucrudile.models.mixins import make_model_mixin

DetailView.url_args = ['(?P<pk>\d+)',]

Listable = make_model_mixin(ListView)
Detailable = make_model_mixin(DetailView)

class Book(Listable, Detailable, Model):
pass

>> Book.get_views()
[ListView, DetailView]

>> Book.get_url_name(ListView)
'book-list'

>> Book.get_list_url()
'/book/list'

>> Book.get_url_patterns()
[<RegexURLPattern book-list book/list>,
<RegexURLPattern book-detail book/detail/<pk>>]
```

The return value of `get_url_patterns()` can be used in `urls.py` (for example, in `patterns('', ..)`).

Here, `ListView` and `DetailView` can be standard generic views, or your own CBVs. As you can see, the only requirement is that, when a view needs an URL argument, it must be specified in the `url_args` attribute of the view class.

`make_model_mixin` automatically patches the given view with utility functions from `views.mixins.ModelActionMixin`, needed to have information about "what does the view do" (action name), and "what paths should point to it" (URLs specification). For more flexibility, it can be better to redefine the views, to subclass `views.mixins.ModelActionMixin`, and to override the needed methods (see documentation).

It is also possible to create the model mixins by yourself (`make_model_mixin` is just a convenience function to automatically create model mixins based on a view) (see documentation).

`django-crucrudile` also provides a convenience function, `auto_patterns_for_app`, that can generate the URL patterns for each Model in an application (using `ContentType` to find the models), and that can be used directly in `urls.py`.
TODO

## Tests

Expand All @@ -66,8 +24,3 @@ This is my first attempt at creating a (hopefully) useful Django application. If
## Contributing

If you feel like you want to contribute to this project, please fork/send patches/submit pull requests ! The documentation and tests really need some improvement. This was my first time writing serious test cases, and my testing code is particularly ugly :(

`django-crucrudile` only consists of 4 files, with 2 of them being just utility functions (`utils.py` and `urls.py`). The two other files are what really makes it working :

* `models/mixins.py` :: Model mixins (one base class) and on-the-run mixin creator (function)
* `views/mixins.py` :: View mixins (one base class)
34 changes: 20 additions & 14 deletions django_crucrudile/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
"""django-crucrudile allows you to create "model mixins", that define
possible actions for this model. Those model mixins allow the model to
be able to generate its URL patterns by itself, so that they can be
included in urls.py using just a call to the get_url_patterns() method
of the model class.
Modules :
-- models.mixins : model mixins and functions
-- views.mixins : view mixins
-- urls : automatic URL patterns functions
-- utils : utility functions
"""
"""django-crucrudile provides URL routing classes, which allows you to
define your URL routing structure using Router and Route classes, and
then to automatically generate an URL pattern structure.
Documentation
-------------
Documentation is built using Sphinx (using static reStructuredText
files stored in ``docs`` and Sphinx-formatted docstrings in modules,
classes and functions).
Use the following command to build the documentation in ``docs/_build``::
sphinx-build -E -c docs -b html -a docs docs/_build
The documentation can also be viewed online, at
https://django-crucrudile.readthedocs.org/en/master/.
"""
__title__ = 'django-crucrudile'
__description__ = 'Model-defined CRUD views & patterns for Django',
__description__ = 'Django URL routing classes',

__version__ = '0.4.7'
__version__ = '0.9.1'

__author__ = 'Hugo Geoffroy'
__author_email__ = 'hugo@pstch.net'
Expand Down
169 changes: 169 additions & 0 deletions django_crucrudile/entities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""An entity is an abstract class of objects that can be used to make
an URL pattern tree.
.. seealso:: In ``django-crucrudile``, there are two classes that
directly subclass :class:`Entity` :
- :class:`django_crucrudile.routers.Router` (concrete)
- :class:`django_crucrudile.routes.base.BaseRoute` (abstract)
"""
from abc import ABCMeta, abstractmethod


class Entity(metaclass=ABCMeta):
"""An entity is an abstract class of objects that can be used to make
an URL pattern tree.
Abstract class that defines an attribute
(:attr:`Entity.index`), and an abstract method
(:func:`Entity.patterns`). Entity implementations should provide the
:func:`Entity.patterns` method, that should return a generator
yielding Django URL patterns.
.. warning:: Abstract class ! Subclasses should define the
abstract :func:`patterns` method, that should return
a generator yielding Django URL objects
(:class:`django.core.urlresolvers.RegexURLPattern` or
`:class:`django.core.urlresolvers.RegexURLResolver`). See
warning in :func:`__init__`.
.. inheritance-diagram:: Entity
"""
index = False
"""
:attribute index: Used when routed entity is registered, to know if
it should be registered as index.
:type index: bool
"""
def __init__(self, index=None):
"""Initialize entity, allow setting :attr:`index` from arguments, and
add ``redirect`` instance attribute
:argument index: See :attr:`index`
"""
if index is not None: # pragma: no cover
self.index = index
self.redirect = None

@abstractmethod
def patterns(self, parents=None,
add_redirect=None,
add_redirect_silent=None): # pragma: no cover
"""Yield URL patterns
.. note::
For argument specification, see implementations of this
abstract function (in particular
:func:`django_crucrudile.routers.Router.patterns`)
.. warning::
Abstract method ! Should be defined by subclasses, and
should return a generator yielding Django URL objects
(``RegexURLPattern`` or ``RegexURLResolver``)
"""
pass

def get_str_tree(self, patterns_kwargs=None,
indent_char=' ', indent_size=2):
"""Return the representation of a entity patterns structure
:argument patterns_kwargs: Keyword arguments to pass to
:func:`patterns`
:type patterns_kwargs: dict
:argument indent_char: String to use for tree indentation
:type indent_char: str
:argument indent_size: Indent size
:type indent_size: int
>>> import tests.unit
>>> from django.db.models import Model
>>> from django_crucrudile.routers import Router, ModelRouter
>>>
>>> # needed to subclass Django Model
>>> __name__ = "tests.doctests"
>>>
>>> class TestModel(Model):
... pass
>>> router = Router(generic=True)
>>>
>>> router.register(TestModel)
>>> print(router.get_str_tree())
... # doctest: +NORMALIZE_WHITESPACE
- Router @ ^
- GenericModelRouter testmodel @ ^testmodel/
- testmodel-list-redirect @ ^$ RedirectView
- testmodel-delete @ ^delete$ DeleteView
- testmodel-update @ ^update$ UpdateView
- testmodel-create @ ^create$ CreateView
- testmodel-detail @ ^detail$ DetailView
- testmodel-list @ ^list$ ListView
"""
def _walk_tree(patterns, level=0):
"""Walk the tree, yielding at tuple of form :
``(level, namespace|router_class|model, callback)``
"""
for pattern in patterns:

callback = (
pattern.callback.__name__
if pattern.callback else None
)

if hasattr(pattern, 'url_patterns'):
# Resolver
# Yield a line with the resolver metadata
_model = getattr(pattern.router, 'model', None)
yield (
level,
pattern.namespace or
"{} {}".format(
pattern.router.__class__.__name__ or '',
_model._meta.model_name if _model else ''
),
pattern.regex.pattern,
callback
)
# Then, yield lines with the pattern subpatterns
# (incrementing level)
for line_tuple in _walk_tree(
pattern.url_patterns, level+1):
yield line_tuple
else:
# Pattern
# Yield a line with the pattern metadata
yield (
level,
pattern.name,
pattern.regex.pattern,
callback
)

def _str_tree(lines):
"""Iterate over the tuple returned by _walk_tree, formatting it."""
for _level, _name, _pattern, _callback in lines:
yield "{} - {} @ {} {}".format(
indent_char*indent_size*_level,
_name or '',
_pattern or '',
_callback or '',
)

patterns_kwargs = patterns_kwargs or {}
patterns = self.patterns(**patterns_kwargs)
pattern_tuples = _walk_tree(patterns)
pattern_lines = _str_tree(pattern_tuples)

return '\n'.join(
pattern_lines
)
Loading

0 comments on commit 0aa90fe

Please sign in to comment.