{ "info": { "author": "Alexey Kinyov", "author_email": "rudy@05bit.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7" ], "description": "django-smarter\n==============\n\nAnother approach for declarative style generic views for Django. I beleive, it's a bit smarter :)\n\nOverview\n--------\n\nSo many times we have to write:\n\n.. sourcecode:: python\n\n @login_required\n def edit_post(request, pk):\n post = get_object_or_404(Post, pk=pk)\n if request.method == 'POST':\n form = EditPostForm(request.POST, instance=post)\n if form.is_valid():\n post = form.save()\n return redirect(post.get_absolute_url())\n else:\n form = EditPostForm()\n return render(request, 'edit_post.html', {'form': form})\n\nRight? Well, it's ok to write some reusable helpers for such repeatable views, but when we don't need sophisticated ones here we go:\n\n.. sourcecode:: python\n\n class PostViews(smarter.GenericViews):\n model = Post\n options = {\n 'add': {\n 'form': NewPostForm,\n 'decorators': (login_required,)\n },\n 'edit': {\n 'form': EditPostForm,\n 'decorators': (login_required,)\n },\n 'remove': {\n 'decorators': (login_required,)\n }\n }\n\nThat's it.\n\n\nChanges in v1.0\n---------------\n\nAPI is finally and completely changed since v0.6 release.\n\nWe've made a \"quantum jump\" by breaking old-and-not-so-good API to new one - solid and nice. Hope you'll like it.\n\nHere are `some hints that may help you with migration `_. I'm actually successfully migrated my real-production project, so the hints are based on \"real-battle\" example.\n\n\nContributors\n------------\n\n* `Fabio Santos `_\n* `Sameer Al-Sakran `_\n\nThank you, comrades! :)\n\nInstallation\n------------\n\nRequirements:\n\n- Django >= 1.4\n\nInstallation::\n \n pip install django-smarter\n\nYou *may* add ``smarter`` to your ``INSTALLED_APPS`` to get default templates and tests, but you *don't have to*:\n\n.. sourcecode:: python\n\n INSTALLED_APPS = (\n # ...\n 'smarter',\n # ...\n )\n\nThen you should define your views and include them in URLs, see `Getting started`_ section below.\n\n\nGetting started\n---------------\n\nCreate your models\n~~~~~~~~~~~~~~~~~~\n\nLet\u2019s define a simple model:\n\n.. sourcecode:: python\n\n class Page(models.Model):\n owner = models.ForeignKey('auth.User')\n title = models.CharField(max_length=100)\n text = models.TextField()\n\n def __unicode__(self):\n return self.title\n\nRegister views\n~~~~~~~~~~~~~~\n\nNow you can add generic views for the model.\n\nIn your `urls.py`:\n\n.. sourcecode:: python\n\n import smarter\n from myapp.models import Page\n\n site = smarter.Site()\n site.register(smarter.GenericViews, Page)\n\n urlpatterns = patterns('',\n url(r'^', include(site.urls)),\n\n # other urls ...\n )\n\nThis code creates generic views for ``Page`` model, accessed by urls:\n\n- /page/\n- /page/add/\n- /page/````/\n- /page/````/edit/\n- /page/````/remove/\n\nCustomize views\n~~~~~~~~~~~~~~~\n\nSubclass from ``smarter.GenericViews`` and set custom options and/or override methods.\n\n.. sourcecode:: python\n\n from django.contrib.auth.decorators import login_required\n import smarter\n from .models import Page\n\n class PageViews(smarter.GenericViews):\n model = Page\n\n options = {\n 'add': {\n 'decorators': (login_required,)\n 'exclude': ('owner',)\n },\n }\n\n def add__save(self, request, form, **kwargs):\n obj = form.save(commit=False)\n obj.owner = request.user\n obj.save()\n return obj\n\nAnd don't forget to register new views in `urls.py`:\n\n.. sourcecode:: python\n\n import smarter\n from myapp.views import PageViews\n\n site = smarter.Site()\n site.register(PageViews) # model argument is not required as model is already set in PageViews\n\n urlpatterns = patterns('',\n url(r'^', include(site.urls)),\n )\n\nCustomize templates\n~~~~~~~~~~~~~~~~~~~\n\nIn the example above each URL by default to template.\n\n====================== ======================= =====================\n URL Template Context\n====================== ======================= =====================\n/page/ myapp/page/index.html {{ objects_list }}\n/page/add/ myapp/page/add.html {{ obj }}, {{ form }}\n/page/````/ myapp/page/details.html {{ obj }}\n/page/````/edit/ myapp/page/edit.html {{ obj }}, {{ form }}\n/page/````/remove/ myapp/page/remove.html {{ obj }}\n====================== ======================= =====================\n\nDefault template search paths are:\n\n.. sourcecode:: python\n\n ('%(app)s/%(model)s/%(action)s.html',\n '%(app)s/%(model)s/%(action)s.ajax.html',\n 'smarter/%(action)s.html',\n 'smarter/_form.html',\n 'smarter/_ajax.html',)\n\nSo, you have some easy way options:\n\n1. you may override matching templates\n2. you may set 'template' key in ``PageViews.options`` for each action\n3. you may override default search paths by settings new ``PageViews.defaults`` (read `Options`_ section for details)\n\nSingleton Site\n~~~~~~~~~~~~~~\n\nA very special instance of `smarter.Site` is in the smarter module. It allows you to register your applications' views outside your urls.py file, and works well with `autodiscover()`.\n\nHere is smarter_views.py in your app:\n\n.. sourcecode:: python\n\n from smarter import site, GenericViews\n from models import Model\n \n class Views(GenericViews):\n model = Model\n\n # ...\n\n site.register(Views)\n\n... And urls.py:\n\n.. sourcecode:: python\n\n from django.conf.urls import patterns, include, url\n import smarter\n\n smarter.autodiscover()\n urlpatterns = patterns('',\n url(r'^', include(smarter.site.urls)),\n )\n\nThis is mostly recommended for non-reusable applications local to your Django project.\n\nAPI reference\n-------------\n\nActions\n~~~~~~~\n\n**Actions** are actually \"ids\" for views. Well, each action has id like 'add', 'edit', 'bind-to-user' and is mapped to view method with underscores instead of '-': `add`, `edit`, `bind_to_user`.\n\nIn ``smarter.GenericViews`` class such actions are defined by default:\n\n======= ================= ========================= ========================\nAction URL View method Named URL\n======= ================= ========================= ========================\nindex / index(``request``) [prefix]-[model]-index\nadd /add/ add(``request``) [prefix]-[model]-add\ndetails /````/ details(``request, pk``) [prefix]-[model]-details\nedit /````/edit/ edit(``request, pk``) [prefix]-[model]-edit\nremove /````/remove/ remove(``request, pk``) [prefix]-[model]-remove\n======= ================= ========================= ========================\n\nWhat is **[prefix]**? Prefix is defined for ``smarter.Site`` instance:\n\n.. sourcecode:: python\n\n site = smarter.Site(prefix='myapp')\n site.register(PageViews)\n # ...\n\nSo, it **can be empty** and URL names without prefix are defined as `[model]-index`. Please, read `Reversing urls`_ section for more details.\n\nOptions\n~~~~~~~\n\n**Options** is a ``GenericViews.options`` dict, class property, it contains actions names as keys and actions parameters as values. Parameters structure is:\n\n.. sourcecode:: python\n\n {\n 'url': ,\n 'form':
,\n 'decorators': ,\n 'fields': ,\n 'exclude': ,\n 'initial': ,\n 'permissions': ,\n 'widgets': ,\n 'help_text': ,\n 'required': ,\n 'template': ,\n 'redirect': \n }\n\nEvery key here is optional. So, here's how options can be defined for views:\n\n.. sourcecode:: python\n\n import smarter\n\n class Views(smarter.GenericViews):\n model = \n\n defaults = \n\n options = {\n '': ,\n '': \n }\n\nAnd here's ``GenericViews.defaults`` class attribute:\n\n.. sourcecode:: python\n\n defaults = {\n 'initial': None,\n 'form': ModelForm,\n 'exclude': None,\n 'fields': None,\n 'labels': None,\n 'widgets': None,\n 'required': None,\n 'help_text': None,\n 'next': None,\n 'template': (\n '%(app)s/%(model)s/%(action)s.html',\n '%(app)s/%(model)s/%(action)s.ajax.html',\n 'smarter/%(action)s.html',\n 'smarter/_form.html',\n 'smarter/_ajax.html',),\n 'decorators': None,\n 'permissions': None,\n }\n\nWhen option value can't be found in options dict for action it's searched in `GenericViews.defaults`. Note, that defaults are applied to **all actions**.\n\nAction names and URLs\n~~~~~~~~~~~~~~~~~~~~~\n\nActions are named so they can be mapped to views methods and they should not override reserved attributes and methods, so they:\n\n1. **must contain only** latin symbols and '_' or '-', **no spaces**\n2. **can't** be in this list: 'model', 'defaults', 'options', 'deny'\n3. **can't** start with '-', '_' or 'get\\_'\n4. **can't** contain '`__`'\n\nSure, you'll get an exception if something goes wrong with that. We're following `'errors should never pass silently'` here.\n\nAnd here's how URLs for default views are defined:\n\n.. sourcecode:: python\n\n {\n 'index': {\n 'url': r'',\n },\n 'details': {\n 'url': r'(?P\\d+)/',\n },\n 'add': {\n 'url': r'add/',\n },\n 'edit': {\n 'url': r'(?P\\d+)/edit/',\n },\n 'remove': {\n 'url': r'(?P\\d+)/remove/',\n }\n }\n\nsmarter.Site\n~~~~~~~~~~~~\n\n| **Site**\\(prefix=None, delim='-')\n| - constructor\n|\n| **register**\\(views, model=None, base_url=None, prefix=None)\n| - method to add your views for model\n|\n| **urls**\n| - property, returns URLs sequence for all registered views that can be included in `urlpatterns`\n| \n| **autodiscover**\n| - method which goes over `settings.INSTALLED_APPS` and looks for apps with `smarter_views` modules, which it imports, so they can register their views.\n\nSite\n++++\n\nConstructor gets two keyword arguments:\n\n1. `prefix=None`, for prefixing URL names for views registered with site object, like '**%(prefix)s**-%(model)s-%(action)s'. If prefix if empty, URLs are named without prefix, like '%(model)s-%(action)s'.\n\n2. `delim='-'`, delimiter for URL names, can be '-', '_' or empty string. URL names are composed with specified delimiter and with uderscore it would be like '%(prefix)s_%(model)s_%(action)s'.\n\nSite.register\n+++++++++++++\n\nThis method gets 1 required argument for views class and optional keyword arguments:\n\n1. `model=None`, model class for views. This argument is required if views class doesn't have 'model' property.\n\n2. `base_url=None`, base URL for views. If empty, then lower-case model name is used, so base URL becomes '%(model)s/'.\n\n3. `prefix=None`, prefix for URL names. If empty, then lower-case model name is used.\n\nsmarter.GenericViews\n~~~~~~~~~~~~~~~~~~~~\n\n| **model**\n| - class property, model class for views\n|\n| **defaults**\n| - class property, dict with default options applied to all actions until being overriden by `options`\n|\n| **options**\n| - class property, dict for views configration, each key corresponds to single action like 'add', 'edit', 'remove' etc.\n|\n| **deny**\\(``request, message=None``)\n| - method, is called when action is not permitted for user, raises ``PermissionDenied`` exception or can return ``HttpResponse`` object for redirecting or rendering some page\n|\n| **get_url**\\(``action, *args, **kwargs``)\n| - method, returns url for given action name\n|\n| **get_form**\\(``request, **kwargs``)\n| - method, returns form for request\n|\n| **get_object**\\(``request, **kwargs``)\n| - method, returns single object for request\n|\n| **get_objects_list**\\(``request, **kwargs``)\n| - method, returns objects for request\n|\n| **get_template**\\(``request_or_action``)\n| - method, returns template name or sequence of template names by action name or per-request\n|\n| **get_param**\\(``self, request_or_action, name, default=None``)\n| - method, returns option parameter by name for action or per-request\n|\n| **get_initial**\\(``self, request``)\n| - method, returns form initial data per-request\n|\n| ****\\(``request, **kwargs``)\n| - method, 1st (starting) handler in default pipeline\n|\n| **__perm**\\(``request, **kwargs``)\n| - method, 2nd handler in default pipeline, checks extended permissions, e.g. per-object permissions (basic checks are handler separatelly)\n|\n| **__form**\\(``request, **kwargs``)\n| - method, 3rd handler in default pipeline, manages form processing\n|\n| **__save**\\(``request, form, **kwargs``)\n| - method, called from **__form** when form is ready to save, saves the form and returns saved instance\n|\n| **__post**\\(``request, **kwargs``)\n| - method, 4th handler in default pipeline for post-processing: save messages, extend render context, etc.\n|\n| **__done**\\(``request, **kwargs``)\n| - method, 5th (last) view handler in default pipeline, performs render or redirect\n\nPipeline\n~~~~~~~~\n\nEach action like 'add', 'edit' or 'remove' is a **pipeline**: a sequence (list) of methods called one after another. A result of each method is passed to the next one.\n\nThe result is either **None** or **dict** or **HttpResponse** object:\n\n1. **None** - result from previous pipeline method is used for next one,\n2. **dict** - result is passed to next pipeline method,\n3. **HttpResponse** - returned immidiately as view response.\n\nFor example, 'edit' action pipeline is: 'edit' -> 'edit__perm' -> 'edit__form' -> 'edit__post' -> 'edit__done'.\n\nNote about **__perm** step. Basic permissions are checked **before** pipeline start view (e.g 'edit'), as if view were decorated with ``permission_required`` decorator. Actualy we're not using decorator, because we need to call our custom ``deny()`` method if permissions are not sufficient, but it's not the key. The key is **you don't need to check basic permissions in custom __perm method, it's necessary for per-object permissions checks.**\n\n========== ===================================== ===================================================\n Method Parameters Result\n========== ===================================== ===================================================\nedit ``request, **kwargs`` 'pk' ``{'obj': obj, 'form': {'instance': obj}}``\n\nedit__perm ``request, **kwargs`` 'obj', 'form' pass (``None``) or ``PermissionDenied`` exception\n\nedit__form ``request, **kwargs`` 'obj', 'form' | ``{'form': form, 'obj': obj, 'form_saved': True}``\n | - form successfully saved\n | ``{'form': form, 'obj': obj}``\n | - first open or form contains errors\n\nedit__post ``request, **kwargs`` pass (``None``) by default\n 'obj', 'form', 'form_saved'\n\nedit__done ``request, **kwargs``\n 'obj', 'form', 'form_saved' render template or redirect to\n ``obj.get_absolute_url()``\n========== ===================================== ===================================================\n\nNote, that in general you won't need to redefine pipeline methods, as in many cases custom behavior can be reached with declarative style using **options**. If you're going too far with overriding views, that may mean you'd better write some views from scratch separate from \"smarter\".\n\nReversing URLs\n~~~~~~~~~~~~~~\n\nEvery action mapped to named URL. Names are composed as::\n\n [site prefix][delimiter][views prefix][delimiter][action]\n\nWhere:\n\n- **site prefix** is 'prefix' parameter in `smarter.Site`_ constructor\n- **delimiter** is 'delim' paratemer in `smarter.Site`_ constructor\n- **views prefix** is 'prefix' parameter in `Site.register`_ method\n\nSo, in `Getting started`_ example named URLs are 'page-add', 'page-edit', 'page-remove', etc., as we don't provide any custom prefixes and delimiter is '-' by default.\n\nPipeline example\n----------------\n\nFor deeper understanding here's an example of custom pipeline for 'edit' action. It's not actually a **recommended** way, as we can reach the same effect without overriding ``edit`` method by defining ``options['edit']['initial']``, but it illustrates the principle of pipeline.\n\n.. sourcecode:: python\n\n import smarter\n\n class PageViews(smarter.GenericViews):\n model = Page\n\n def edit(request, pk=None):\n # Custom initial title\n initial = {'title': request.GET.get('title': '')}\n return {\n 'obj': self.get_object(request, pk=pk),\n 'form' {'initial': initial, 'instance': obj}\n }\n\n def edit__perm(request, **kwargs):\n # Custom permission check\n if kwargs['obj'].owner != request.user:\n return self.deny(request)\n\n def edit__form(request, **kwargs):\n # Actually, nothing custom here, it's totally generic:\n # we should validate & save form and then return dict\n # with 'form_saved' set to True if it's ok.\n kwargs['form'] = self.get_form(request, **kwargs)\n if kwargs['form'].is_valid():\n kwargs['obj'] = self.edit__save(request, **kwargs)\n kwargs['form_saved'] = True\n return kwargs\n\n def edit__done(request, obj=None, form=None, form_saved=None):\n # Custom redirect to pages index on success\n if form_saved:\n # Success, redirecting!\n return redirect(self.get_url('index'))\n else:\n # Start edit or form has errors\n return render(request, self.get_template(request),\n {'obj': obj, 'form': form})\n\nComplete example\n----------------\n\n| You may look at complete example source here:\n| https://github.com/05bit/django-smarter/tree/master/example\n\nLicense\n-------\n\nCopyright (c) 2013, Alexey Kinyov \nLicensed under BSD, see LICENSE for more details.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/05bit/django-smarter", "keywords": "django,application,scaffolding,crud,views,utility", "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "django-smarter", "package_url": "https://pypi.org/project/django-smarter/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-smarter/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/05bit/django-smarter" }, "release_url": "https://pypi.org/project/django-smarter/1.1/", "requires_dist": null, "requires_python": null, "summary": "Smarter declarative style generic views for Django.", "version": "1.1" }, "last_serial": 772718, "releases": { "0.4": [ { "comment_text": "", "digests": { "md5": "58b669e27c7b0168c3365526dd289d16", "sha256": "ffb559a58585afc4d7cda1d0986166bd21a76d186a76d01cf79510ba5cca4644" }, "downloads": -1, "filename": "django-smarter-0.4.tar.gz", "has_sig": false, "md5_digest": "58b669e27c7b0168c3365526dd289d16", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5571, "upload_time": "2012-03-05T15:13:47", "url": "https://files.pythonhosted.org/packages/43/c5/e431669ab358b10f7aeecec9f9bee7f997659c2a6b33a91854ed57f4653f/django-smarter-0.4.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "470e52ef60c49c1c26a28408249f7869", "sha256": "c04b6d55b33ce7909edb5d0fb878b8dd87e4a0b9c8f992ded7d0e012de413e02" }, "downloads": -1, "filename": "django-smarter-0.4.1.tar.gz", "has_sig": false, "md5_digest": "470e52ef60c49c1c26a28408249f7869", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7466, "upload_time": "2013-01-21T18:36:04", "url": "https://files.pythonhosted.org/packages/ef/ad/79592d0d1ed255b8e8e5397df5673b29c3aa2636827d52724231eb03e99e/django-smarter-0.4.1.tar.gz" } ], "0.5": [ { "comment_text": "", "digests": { "md5": "5055557f6e6e9c348aeac7058456d17b", "sha256": "0161c2df5b73825a22c86a22161856deba5c23a6f96a1f713196eac130c6e8cf" }, "downloads": -1, "filename": "django-smarter-0.5.tar.gz", "has_sig": false, "md5_digest": "5055557f6e6e9c348aeac7058456d17b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8296, "upload_time": "2013-01-21T19:31:57", "url": "https://files.pythonhosted.org/packages/f0/7a/03ee98244ad50d68afc4ae168541a08ddfd23f7592a79391845cac7c6364/django-smarter-0.5.tar.gz" } ], "1.0b": [ { "comment_text": "", "digests": { "md5": "8af22273536107f2424052a25d070d95", "sha256": "e29ddaee8df54c957b469f7946eec4a0fa7c85f15a966445bb221e35ed8372d6" }, "downloads": -1, "filename": "django-smarter-1.0b.tar.gz", "has_sig": false, "md5_digest": "8af22273536107f2424052a25d070d95", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15474, "upload_time": "2013-03-28T09:18:28", "url": "https://files.pythonhosted.org/packages/11/fd/ea373e08184644b056940a75e375d5b4239fe87f8b00b4ea4a2f35df8953/django-smarter-1.0b.tar.gz" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "e7fa88adc550f4c3144a744b1f7830a0", "sha256": "c5843eef118f406c22cea7f92e1913568029c52276ae09f61c2f41e4c9118214" }, "downloads": -1, "filename": "django-smarter-1.1.tar.gz", "has_sig": false, "md5_digest": "e7fa88adc550f4c3144a744b1f7830a0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16086, "upload_time": "2013-05-14T08:59:10", "url": "https://files.pythonhosted.org/packages/a4/e0/16e906e0d777aad2fd42d70e1f45f5b8c3c092cb7b38e85dae00fb77381c/django-smarter-1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "e7fa88adc550f4c3144a744b1f7830a0", "sha256": "c5843eef118f406c22cea7f92e1913568029c52276ae09f61c2f41e4c9118214" }, "downloads": -1, "filename": "django-smarter-1.1.tar.gz", "has_sig": false, "md5_digest": "e7fa88adc550f4c3144a744b1f7830a0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16086, "upload_time": "2013-05-14T08:59:10", "url": "https://files.pythonhosted.org/packages/a4/e0/16e906e0d777aad2fd42d70e1f45f5b8c3c092cb7b38e85dae00fb77381c/django-smarter-1.1.tar.gz" } ] }