diff options
author | SVN-Git Migration <python-modules-team@lists.alioth.debian.org> | 2015-10-08 11:51:47 -0700 |
---|---|---|
committer | SVN-Git Migration <python-modules-team@lists.alioth.debian.org> | 2015-10-08 11:51:47 -0700 |
commit | 71ac4bf01bb5d6dad8eb77b783a0b06cf4fdeb1e (patch) | |
tree | 267cac494b95ea0918487569768a4767f96b41ab | |
parent | d03c8d2d2efaf848b01e96863c29f46ce3a0db21 (diff) | |
download | python-django-tagging-71ac4bf01bb5d6dad8eb77b783a0b06cf4fdeb1e.tar python-django-tagging-71ac4bf01bb5d6dad8eb77b783a0b06cf4fdeb1e.tar.gz |
Imported Upstream version 0.2.1+svn154upstream/0.2.1+svn154
76 files changed, 84 insertions, 4659 deletions
diff --git a/.svn/all-wcprops b/.svn/all-wcprops deleted file mode 100644 index dd05e4f..0000000 --- a/.svn/all-wcprops +++ /dev/null @@ -1,41 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 23 -/svn/!svn/ver/147/trunk -END -LICENSE.txt -K 25 -svn:wc:ra_dav:version-url -V 35 -/svn/!svn/ver/114/trunk/LICENSE.txt -END -INSTALL.txt -K 25 -svn:wc:ra_dav:version-url -V 34 -/svn/!svn/ver/93/trunk/INSTALL.txt -END -CHANGELOG.txt -K 25 -svn:wc:ra_dav:version-url -V 37 -/svn/!svn/ver/131/trunk/CHANGELOG.txt -END -setup.py -K 25 -svn:wc:ra_dav:version-url -V 32 -/svn/!svn/ver/114/trunk/setup.py -END -MANIFEST.in -K 25 -svn:wc:ra_dav:version-url -V 35 -/svn/!svn/ver/121/trunk/MANIFEST.in -END -README.txt -K 25 -svn:wc:ra_dav:version-url -V 33 -/svn/!svn/ver/93/trunk/README.txt -END diff --git a/.svn/dir-prop-base b/.svn/dir-prop-base deleted file mode 100644 index dcdc624..0000000 --- a/.svn/dir-prop-base +++ /dev/null @@ -1,6 +0,0 @@ -K 10 -svn:ignore -V 7 -*.pyc
- -END diff --git a/.svn/entries b/.svn/entries deleted file mode 100644 index 3421631..0000000 --- a/.svn/entries +++ /dev/null @@ -1,238 +0,0 @@ -9 - -dir -147 -http://django-tagging.googlecode.com/svn/trunk -http://django-tagging.googlecode.com/svn - - - -2008-08-20T23:28:50.903432Z -147 -doug.napoleone -has-props - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -83e7428b-ec2a-0410-86f2-bf466d0e5e72 - -tagging -dir - -LICENSE.txt -file - - - - -2008-08-21T18:05:41.000000Z -aeb0c61476a9b52a02584fe79e73ff84 -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -2671 - -INSTALL.txt -file - - - - -2008-08-21T18:05:41.000000Z -2298a5dfd7903940c220d522f991f5ed -2007-08-20T09:19:55.193228Z -93 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -534 - -CHANGELOG.txt -file - - - - -2008-08-21T18:05:41.000000Z -0591d66a8ffdbde408f3321407270f6d -2008-01-22T13:02:34.889764Z -131 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -4108 - -setup.py -file - - - - -2008-08-21T18:05:41.000000Z -b40563b2c098f951ba96a45a602fa356 -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -2510 - -docs -dir - -MANIFEST.in -file - - - - -2008-08-21T18:05:41.000000Z -b4e06d199cc9fd3aa1e9f0fc3c6a7805 -2008-01-12T14:59:20.749002Z -121 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -168 - -README.txt -file - - - - -2008-08-21T18:05:41.000000Z -7726a0726e463ffe68549485e57f8560 -2007-08-20T09:19:55.193228Z -93 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -309 - diff --git a/.svn/format b/.svn/format deleted file mode 100644 index ec63514..0000000 --- a/.svn/format +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/.svn/prop-base/CHANGELOG.txt.svn-base b/.svn/prop-base/CHANGELOG.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/CHANGELOG.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/prop-base/INSTALL.txt.svn-base b/.svn/prop-base/INSTALL.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/INSTALL.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/prop-base/LICENSE.txt.svn-base b/.svn/prop-base/LICENSE.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/LICENSE.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/prop-base/MANIFEST.in.svn-base b/.svn/prop-base/MANIFEST.in.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/MANIFEST.in.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/prop-base/README.txt.svn-base b/.svn/prop-base/README.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/README.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/prop-base/setup.py.svn-base b/.svn/prop-base/setup.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/.svn/prop-base/setup.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/.svn/text-base/CHANGELOG.txt.svn-base b/.svn/text-base/CHANGELOG.txt.svn-base deleted file mode 100644 index 1a8c360..0000000 --- a/.svn/text-base/CHANGELOG.txt.svn-base +++ /dev/null @@ -1,105 +0,0 @@ -======================== -Django Tagging Changelog -======================== - - -SVN Trunk Changes: ------------------- - -* Added a ``tagging.generic`` module for working with list of objects - which have generic relations, containing a ``fetch_content_objects`` - function for retrieving content objects for a list of ``TaggedItem``s - using ``number_of_content_types + 1`` queries rather than the - ``number_of_tagged_items * 2`` queries you'd get by iterating over the - list and accessing each item's ``object`` attribute. - -* Added a ``usage`` method to ``ModelTagManager``. - -* ``TaggedItemManager``'s methods now accept a ``QuerySet`` or a - ``Model`` class. If a ``QuerySet`` is given, it will be used as the - basis for the ``QuerySet``s the methods return, so can be used to - restrict results to a subset of a model's instances. The - `tagged_object_list`` generic view and ModelTaggedItemManager`` - manager have been updated accordingly. - -* Removed ``tagging\tests\runtests.py``, as tests can be run with - ``django-admin.py test --settings=tagging.tests.settings``. - -* A ``tagging.TagDescriptor`` is now added to models when registered. - This returns a ``tagging.managers.ModelTagManager`` when accessed on a - model class, and provide access to and control over tags when used on - an instance. - -* Added ``tagging.register`` to register models with the tagging app. - Initially, a ``tagging.managers.ModelTaggedItemManager`` is added for - convenient access to tagged items. - -* Moved ``TagManager`` and ``TaggedItemManager`` to ``models.py`` - gets - rid of some import related silliness, as ``TagManager`` needs access - to ``TaggedItem``. - -Version 0.2.1, 16th Jan 2008: ------------------------------ - -* Fixed a bug with space-delimited tag input handling - duplicates - weren't being removed and the list of tag names wasn't sorted. - -Version 0.2, 12th Jan 2008: ---------------------------- - -Packaged from revision 122 in Subversion; download at -http://django-tagging.googlecode.com/files/tagging-0.2.zip - -* Added a ``tag_cloud_for_model`` template tag. - -* Added a ``MAX_TAG_LENGTH`` setting. - -* Multi-word tags are here - simple space-delimited input still works. - Double quotes and/or commas are used to delineate multi- word tags. - As far as valid tag contents - anything goes, at least initially. - -* BACKWARDS-INCOMPATIBLE CHANGE - ``django.utils.get_tag_name_list`` and - related regular expressions have been removed in favour of a new tag - input parsing function, ``django.utils.parse_tag_input``. - -* BACKWARDS-INCOMPATIBLE CHANGE - ``Tag`` and ``TaggedItem`` no longer - declare an explicit ``db_table``. If you can't rename your tables, - you'll have to put these back in manually. - -* Fixed a bug in calculation of logarithmic tag clouds - ``font_size`` - attributes were not being set in some cases when the least used tag in - the cloud had been used more than once. - -* For consistency of return type, ``TaggedItemManager.get_by_model`` now - returns an empty ``QuerySet`` instead of an empty ``list`` if - non-existent tags were given. - -* Fixed a bug caused by ``cloud_for_model`` not passing its - ``distribution`` argument to ``calculate_cloud``. - -* Added ``TaggedItemManager.get_union_by_model`` for looking up items - tagged with any one of a list of tags. - -* Added ``TagManager.add_tag`` for adding a single extra tag to an - object. - -* Tag names can now be forced to lowercase before they are saved to the - database by adding the appropriate ``FORCE_LOWERCASE_TAGS`` setting to - your project's settings module. This feature defaults to being off. - -* Fixed a bug where passing non-existent tag names to - ``TaggedItemManager.get_by_model`` caused database errors with some - backends. - -* Added ``tagged_object_list`` generic view for displaying paginated - lists of objects for a given model which have a given tag, and - optionally related tags for that model. - - -Version 0.1, 30th May 2007: ---------------------------- - -Packaged from revision 79 in Subversion; download at -http://django-tagging.googlecode.com/files/tagging-0.1.zip - -* First packaged version using distutils. diff --git a/.svn/text-base/INSTALL.txt.svn-base b/.svn/text-base/INSTALL.txt.svn-base deleted file mode 100644 index 7f90cca..0000000 --- a/.svn/text-base/INSTALL.txt.svn-base +++ /dev/null @@ -1,14 +0,0 @@ -Thanks for downloading django-tagging. - -To install it, run the following command inside this directory: - - python setup.py install - -Or if you'd prefer you can simply place the included ``tagging`` -directory somewhere on your Python path, or symlink to it from -somewhere on your Python path; this is useful if you're working from a -Subversion checkout. - -Note that this application requires Python 2.3 or later, and Django -0.96 or later. You can obtain Python from http://www.python.org/ and -Django from http://www.djangoproject.com/.
\ No newline at end of file diff --git a/.svn/text-base/LICENSE.txt.svn-base b/.svn/text-base/LICENSE.txt.svn-base deleted file mode 100644 index 1654536..0000000 --- a/.svn/text-base/LICENSE.txt.svn-base +++ /dev/null @@ -1,55 +0,0 @@ -Django Tagging --------------- - -Copyright (c) 2007, Jonathan Buchanan - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Initially based on code from James Bennett's Cab: - -Cab ---- - -Copyright (c) 2007, James Bennett -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of the author nor the names of other - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file diff --git a/.svn/text-base/MANIFEST.in.svn-base b/.svn/text-base/MANIFEST.in.svn-base deleted file mode 100644 index 618270b..0000000 --- a/.svn/text-base/MANIFEST.in.svn-base +++ /dev/null @@ -1,7 +0,0 @@ -include CHANGELOG.txt -include INSTALL.txt -include LICENSE.txt -include MANIFEST.in -include README.txt -recursive-include docs *.txt -recursive-include tagging/tests *.txt diff --git a/.svn/text-base/README.txt.svn-base b/.svn/text-base/README.txt.svn-base deleted file mode 100644 index acb2d47..0000000 --- a/.svn/text-base/README.txt.svn-base +++ /dev/null @@ -1,10 +0,0 @@ -============== -Django Tagging -============== - -This is a generic tagging application for Django projects - -For installation instructions, see the file "INSTALL.txt" in this -directory; for instructions on how to use this application, and on -what it provides, see the file "overview.txt" in the "docs/" -directory.
\ No newline at end of file diff --git a/.svn/text-base/setup.py.svn-base b/.svn/text-base/setup.py.svn-base deleted file mode 100644 index 66daf3e..0000000 --- a/.svn/text-base/setup.py.svn-base +++ /dev/null @@ -1,72 +0,0 @@ -""" -Based entirely on Django's own ``setup.py``. -""" -import os -from distutils.command.install import INSTALL_SCHEMES -from distutils.core import setup - -def fullsplit(path, result=None): - """ - Split a pathname into components (the opposite of os.path.join) in a - platform-neutral way. - """ - if result is None: - result = [] - head, tail = os.path.split(path) - if head == '': - return [tail] + result - if head == path: - return result - return fullsplit(head, [tail] + result) - -# Tell distutils to put the data_files in platform-specific installation -# locations. See here for an explanation: -# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb -for scheme in INSTALL_SCHEMES.values(): - scheme['data'] = scheme['purelib'] - -# Compile the list of packages available, because distutils doesn't have -# an easy way to do this. -packages, data_files = [], [] -root_dir = os.path.dirname(__file__) -tagging_dir = os.path.join(root_dir, 'tagging') -pieces = fullsplit(root_dir) -if pieces[-1] == '': - len_root_dir = len(pieces) - 1 -else: - len_root_dir = len(pieces) - -for dirpath, dirnames, filenames in os.walk(tagging_dir): - # Ignore dirnames that start with '.' - for i, dirname in enumerate(dirnames): - if dirname.startswith('.'): del dirnames[i] - if '__init__.py' in filenames: - packages.append('.'.join(fullsplit(dirpath)[len_root_dir:])) - elif filenames: - data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) - -# Dynamically calculate the version based on tagging.VERSION -version_tuple = __import__('tagging').VERSION -if version_tuple[2] is not None: - version = "%d.%d_%s" % version_tuple -else: - version = "%d.%d" % version_tuple[:2] - -setup( - name = 'tagging', - version = version, - description = 'Generic tagging application for Django', - author = 'Jonathan Buchanan', - author_email = 'jonathan.buchanan@gmail.com', - url = 'http://code.google.com/p/django-tagging/', - packages = packages, - data_files = data_files, - classifiers = ['Development Status :: 4 - Beta', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Utilities'], -) diff --git a/docs/.svn/all-wcprops b/docs/.svn/all-wcprops deleted file mode 100644 index 6104b64..0000000 --- a/docs/.svn/all-wcprops +++ /dev/null @@ -1,11 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 28 -/svn/!svn/ver/133/trunk/docs -END -overview.txt -K 25 -svn:wc:ra_dav:version-url -V 41 -/svn/!svn/ver/133/trunk/docs/overview.txt -END diff --git a/docs/.svn/entries b/docs/.svn/entries deleted file mode 100644 index 643c736..0000000 --- a/docs/.svn/entries +++ /dev/null @@ -1,62 +0,0 @@ -9 - -dir -147 -http://django-tagging.googlecode.com/svn/trunk/docs -http://django-tagging.googlecode.com/svn - - - -2008-04-30T22:29:01.189094Z -133 -jonathan.buchanan - - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -83e7428b-ec2a-0410-86f2-bf466d0e5e72 - -overview.txt -file - - - - -2008-08-21T18:05:41.000000Z -6ff98ba2ec163f1556bc41693647443a -2008-04-30T22:29:01.189094Z -133 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -29385 - diff --git a/docs/.svn/format b/docs/.svn/format deleted file mode 100644 index ec63514..0000000 --- a/docs/.svn/format +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/docs/.svn/prop-base/overview.txt.svn-base b/docs/.svn/prop-base/overview.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/docs/.svn/prop-base/overview.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/docs/.svn/text-base/overview.txt.svn-base b/docs/.svn/text-base/overview.txt.svn-base deleted file mode 100644 index 9641f03..0000000 --- a/docs/.svn/text-base/overview.txt.svn-base +++ /dev/null @@ -1,912 +0,0 @@ -============== -Django Tagging -============== - -A generic tagging application for `Django`_ projects, which allows -association of a number of tags with any Django model instance and makes -retrieval of tags simple. - -.. _`Django`: http://www.djangoproject.com - -.. contents:: - :depth: 3 - - -Installation -============ - -Installing an official release ------------------------------- - -Official releases are made available from -http://code.google.com/p/django-tagging/ - -Source distribution -~~~~~~~~~~~~~~~~~~~ - -Download the .zip distribution file and unpack it. Inside is a script -named ``setup.py``. Enter this command:: - - python setup.py install - -...and the package will install automatically. - -Windows installer -~~~~~~~~~~~~~~~~~ - -A Windows installer is also made available - download the .exe -distribution file and launch it to install the application. - -An uninstaller will also be created, accessible through Add/Remove -Programs in your Control Panel. - -Installing the development version ----------------------------------- - -Alternatively, if you'd like to update Django Tagging occasionally to pick -up the latest bug fixes and enhancements before they make it into an -official release, perform a `Subversion`_ checkout instead. The following -command will check the application's development branch out to an -``tagging-trunk`` directory:: - - svn checkout http://django-tagging.googlecode.com/svn/trunk/ tagging-trunk - -Add the resulting folder to your `PYTHONPATH`_ or symlink (`junction`_, -if you're on Windows) the ``tagging`` directory inside it into a -directory which is on your PYTHONPATH, such as your Python -installation's ``site-packages`` directory. - -You can verify that the application is available on your PYTHONPATH by -opening a Python interpreter and entering the following commands:: - - >>> import tagging - >>> tagging.VERSION - (0, 3, 'pre') - -When you want to update your copy of the Django Tagging source code, run -the command ``svn update`` from within the ``tagging-trunk`` directory. - -.. caution:: - - The development version may contain bugs which are not present in the - release version and introduce backwards-incompatible changes. - - If you're tracking trunk, keep an eye on the `CHANGELOG`_ and the - `backwards-incompatible changes wiki page`_ before you update your - copy of the source code. - -.. _`Subversion`: http://subversion.tigris.org -.. _`PYTHONPATH`: http://docs.python.org/tut/node8.html#SECTION008110000000000000000 -.. _`junction`: http://www.microsoft.com/technet/sysinternals/FileAndDisk/Junction.mspx -.. _`CHANGELOG`: http://django-tagging.googlecode.com/svn/trunk/CHANGELOG.txt -.. _`backwards-incompatible changes wiki page`: http://code.google.com/p/django-tagging/wiki/BackwardsIncompatibleChanges - -Using Django Tagging in your applications ------------------------------------------ - -Once you've installed Django Tagging and want to use it in your Django -applications, do the following: - - 1. Put ``'tagging'`` in your ``INSTALLED_APPS`` setting. - 2. Run the command ``manage.py syncdb``. - -The ``syncdb`` command creates the necessary database tables and -creates permission objects for all installed apps that need them. - -That's it! - - -Settings -======== - -Some of the application's behaviour can be configured by adding the -appropriate settings to your project's settings file. - -The following settings are available: - -FORCE_LOWERCASE_TAGS --------------------- - -Default: ``False`` - -A boolean that turns on/off forcing of all tag names to lowercase before -they are saved to the database. - -MAX_TAG_LENGTH --------------- - -Default: ``50`` - -An integer which specifies the maximum length which any tag is allowed -to have. This is used for validation in the ``django.contrib.admin`` -application and in any ``newforms`` forms automatically generated using -``ModelForm``. - - -Registering your models -======================= - -**New in developement version** - -Your Django models can be registered with the tagging application to -access some additional tagging-related features. - -.. note:: - - You don't *have* to register your models in order to use them with - the tagging application - many of the features added by registration - are just convenience wrappers around the tagging API provided by the - ``Tag`` and ``TaggedItem`` models and their managers, as documented - further below. - -The ``register`` function -------------------------- - -To register a model, import the ``tagging`` module and call its -``register`` function, like so:: - - from django.db import models - - import tagging - - class Widget(models.Model): - name = models.CharField(max_length=50) - - tagging.register(Widget) - -The following argument is required: - -``model`` - The model class to be registered. - - An exception will be raised if you attempt to register the same class - more than once. - -The following arguments are optional, with some recommended defaults - -take care to specify different attribute names if the defaults clash -with your model class' definition: - -``tag_descriptor_attr`` - The name of an attribute in the model class which will hold a tag - descriptor for the model. Default: ``'tags'`` - - See `TagDescriptor`_ below for details about the use of this - descriptor. - -``tagged_item_manger_attr`` - The name of an attribute in the model class which will hold a custom - manager for accessing tagged items for the model. Default: - ``'tagged'``. - - See `ModelTaggedItemManager`_ below for details about the use of this - manager. - -``TagDescriptor`` ------------------ - -When accessed through the model class itself, this descriptor will return -a ``ModelTagManager`` for the model. See `ModelTagManager`_ below for -more details about its use. - -When accessed through a model instance, this descriptor provides a handy -means of retrieving, updating and deleting the instance's tags. For -example:: - - >>> widget = Widget.objects.create(name='Testing descriptor') - >>> widget.tags - [] - >>> widget.tags = 'toast, melted cheese, butter' - >>> widget.tags - [<Tag: butter>, <Tag: melted cheese>, <Tag: toast>] - >>> del widget.tags - >>> widget.tags - [] - -``ModelTagManager`` -------------------- - -A manager for retrieving tags used by a particular model. - -Defines the following methods: - -* ``get_query_set()`` -- as this method is redefined, any ``QuerySets`` - created by this model will be initially restricted to contain the - distinct tags used by all the model's instances. - -* ``cloud(*args, **kwargs)`` -- creates a list of tags used by the - model's instances, with ``count`` and ``font_size`` attributes set for - use in displaying a tag cloud. - - See the documentation on ``Tag``'s manager's `cloud_for_model method`_ - for information on additional arguments which can be given. - -* ``related(self, tags, *args, **kwargs)`` -- creates a list of tags - used by the model's instances, which are also used by all instance - which have the given ``tags``. - - See the documentation on ``Tag``'s manager's - `related_for_model method`_ for information on additional arguments - which can be given. - -* ``usage(self, *args, **kwargs))`` -- creates a list of tags used by - the model's instances, with optional usages counts, restriction based - on usage counts and restriction of the model instances from which - usage and counts are determined. - - See the documentation on ``Tag``'s manager's `usage_for_model method`_ - for information on additional arguments which can be given. - -Example usage:: - - # Create a ``QuerySet`` of tags used by Widget instances - Widget.tags.all() - - # Retrieve a list of tags used by Widget instances with usage counts - Widget.tags.usage(counts=True) - - # Retrieve tags used by instances of WIdget which are also tagged with - # 'cheese' and 'toast' - Widget.tags.related(['cheese', 'toast'], counts=True, min_count=3) - -``ModelTaggedItemManager`` --------------------------- - -A manager for retrieving model instance for a particular model, based on -their tags. - -* ``related_to(obj, queryset=None, num=None)`` -- creates a list - of model instances which are related to ``obj``, based on its tags. If - a ``queryset`` argument is provided, it will be used to restrict the - resulting list of model instances. - - If ``num`` is given, a maximum of ``num`` instances will be returned. - -* ``with_all(tags, queryset=None)`` -- creates a ``QuerySet`` containing - model instances which are tagged with *all* the given tags. If a - ``queryset`` argument is provided, it will be used as the basis for - the resulting ``QuerySet``. - -* ``with_any(tags, queryset=None)`` -- creates a ``QuerySet`` containing model - instances which are tagged with *any* the given tags. If a ``queryset`` - argument is provided, it will be used as the basis for the resulting - ``QuerySet``. - - -Tags -==== - -Tags are represented by the ``Tag`` model, which lives in the -``tagging.models`` module. - -API reference -------------- - -Fields -~~~~~~ - -``Tag`` objects have the following fields: - -* ``name`` -- The name of the tag. This is a unique value. - -Manager functions -~~~~~~~~~~~~~~~~~ - -The ``Tag`` model has a custom manager which has the following helper -methods: - -* ``update_tags(obj, tag_names)`` -- updates tags associated with an - object. - - ``tag_names`` is a string containing tag names with which ``obj`` - should be tagged. - - If ``tag_names`` is ``None`` or ``''``, the object's tags will be - cleared. - -* ``add_tag(obj, tag_name)`` -- associates a tag with an an object. - - ``tag_name`` is a string containing a tag name with which ``obj`` - should be tagged. - -* ``get_for_object(obj)`` -- returns a ``QuerySet`` containing all - ``Tag`` objects associated with ``obj``. - -.. _`usage_for_model method`: - -* ``usage_for_model(model, counts=False, min_count=None, filters=None)`` - -- returns a list of ``Tag`` objects associated with instances of - ``model``. - - If ``counts`` is ``True``, a ``count`` attribute will be added to each - tag, indicating how many times it has been associated with instances - of ``model``. - - If ``min_count`` is given, only tags which have a ``count`` greater - than or equal to ``min_count`` will be returned. Passing a value for - ``min_count`` implies ``counts=True``. - - To limit the tags (and counts, if specified) returned to those used by - a subset of the model's instances, pass a dictionary of field lookups - to be applied to ``model`` as the ``filters`` argument. - -.. _`related_for_model method`: - -* ``related_for_model(tags, Model, counts=False, min_count=None)`` - -- returns a list of tags related to a given list of tags - that is, - other tags used by items which have all the given tags. - - If ``counts`` is ``True``, a ``count`` attribute will be added to each - tag, indicating the number of items which have it in addition to the - given list of tags. - - If ``min_count`` is given, only tags which have a ``count`` greater - than or equal to ``min_count`` will be returned. Passing a value for - ``min_count`` implies ``counts=True``. - -.. _`cloud_for_model method`: - -* ``cloud_for_model(Model, steps=4, distribution=LOGARITHMIC, - filters=None, min_count=None)`` -- returns a list of the distinct - ``Tag`` objects associated with instances of ``Model``, each having a - ``count`` attribute as above and an additional ``font_size`` - attribute, for use in creation of a tag cloud (a type of weighted - list). - - ``steps`` defines the number of font sizes available - ``font_size`` - may be an integer between ``1`` and ``steps``, inclusive. - - ``distribution`` defines the type of font size distribution algorithm - which will be used - logarithmic or linear. It must be either - ``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``. - - To limit the tags displayed in the cloud to those associated with a - subset of the Model's instances, pass a dictionary of field lookups to - be applied to the given Model as the ``filters`` argument. - - To limit the tags displayed in the cloud to those with a ``count`` - greater than or equal to ``min_count``, pass a value for the - ``min_count`` argument. - -**New in development version** - -* ``usage_for_queryset(queryset, counts=False, min_count=None)`` -- - Obtains a list of tags associated with instances of a model contained - in the given queryset. - - If ``counts`` is True, a ``count`` attribute will be added to each tag, - indicating how many times it has been used against the Model class in - question. - - If ``min_count`` is given, only tags which have a ``count`` greater - than or equal to ``min_count`` will be returned. - - Passing a value for ``min_count`` implies ``counts=True``. - -Basic usage ------------ - -Tagging objects and retrieving an object's tags -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Objects may be tagged using the ``update_tags`` helper function:: - - >>> from shop.apps.products.models import Widget - >>> from tagging.models import Tag - >>> widget = Widget.objects.get(pk=1) - >>> Tag.objects.update_tags(widget, 'house thing') - -Retrieve tags for an object using the ``get_for_object`` helper -function:: - - >>> Tag.objects.get_for_object(widget) - [<Tag: house>, <Tag: thing>] - -Tags are created, associated and unassociated accordingly when you use -``update_tags`` and ``add_tags``:: - - >>> Tag.objects.update_tags(widget, 'house monkey') - >>> Tag.objects.get_for_object(widget) - [<Tag: house>, <Tag: monkey>] - >>> Tag.objects.add_tag(widget, 'tiles') - >>> Tag.objects.get_for_object(widget) - [<Tag: house>, <Tag: monkey>, <Tag: tiles>] - -Clear an object's tags by passing ``None`` or ``''`` to -``update_tags``:: - - >>> Tag.objects.update_tags(widget, None) - >>> Tag.objects.get_for_object(widget) - [] - -Retrieving tags used by a particular model -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To retrieve all tags used for a particular model, use the -``get_for_model`` helper function:: - - >>> widget1 = Widget.objects.get(pk=1) - >>> Tag.objects.update_tags(widget1, 'house thing') - >>> widget2 = Widget.objects.get(pk=2) - >>> Tag.objects.update_tags(widget2, 'cheese toast house') - >>> Tag.objects.usage_for_model(Widget) - [<Tag: cheese>, <Tag: house>, <Tag: thing>, <Tag: toast>] - -To get a count of how many times each tag was used for a particular -model, pass in ``True`` for the ``counts`` argument:: - - >>> tags = Tag.objects.usage_for_model(Widget, counts=True) - >>> [(tag.name, tag.count) for tag in tags] - [('cheese', 1), ('house', 2), ('thing', 1), ('toast', 1)] - -To get counts and limit the tags returned to those with counts above a -certain size, pass in a ``min_count`` argument:: - - >>> tags = Tag.objects.usage_for_model(Widget, min_count=2) - >>> [(tag.name, tag.count) for tag in tags] - [('house', 2)] - -You can also specify a dictionary of `field lookups`_ to be used to -restrict the tags and counts returned based on a subset of the -model's instances. For example, the following would retrieve all tags -used on Widgets created by a user named Alan which have a size -greater than 99:: - - >>> Tag.objects.usage_for_model(Widget, filters=dict(size__gt=99, user__username='Alan')) - -.. _`field lookups`: http://www.djangoproject.com/documentation/db-api/#field-lookups - -**New in development version** - -The ``usage_for_queryset`` method allows you to pass a pre-filtered -queryset to be used when determining tag usage:: - - >>> Tag.objects.usage_for_queryset(Widget.objects.filter(size__gt=99, user__username='Alan')) - -Tag input ---------- - -Tag input from users is treated as follows: - -* If the input doesn't contain any commas or double quotes, it is simply - treated as a space-delimited list of tag names. - -* If the input does contain either of these characters, we parse the - input like so: - - * Groups of characters which appear between double quotes take - precedence as multi-word tags (so double quoted tag names may - contain commas). An unclosed double quote will be ignored. - - * For the remaining input, if there are any unquoted commas in the - input, the remainder will be treated as comma-delimited. Otherwise, - it will be treated as space-delimited. - -Examples: - -====================== ======================================= ================================================ -Tag input string Resulting tags Notes -====================== ======================================= ================================================ -apple ball cat [``apple``], [``ball``], [``cat``] No commas, so space delimited -apple, ball cat [``apple``], [``ball cat``] Comma present, so comma delimited -"apple, ball" cat dog [``apple, ball``], [``cat``], [``dog``] All commas are quoted, so space delimited -"apple, ball", cat dog [``apple, ball``], [``cat dog``] Contains an unquoted comma, so comma delimited -apple "ball cat" dog [``apple``], [``ball cat``], [``dog``] No commas, so space delimited -"apple" "ball dog [``apple``], [``ball``], [``dog``] Unclosed double quote is ignored -====================== ======================================= ================================================ - - -Tagged items -============ - -The relationship between a ``Tag`` and an object is represented by -the ``TaggedItem`` model, which lives in the ``tagging.models`` -module. - -API reference -------------- - -Fields -~~~~~~ - -``TaggedItem`` objects have the following fields: - -* ``tag`` -- The ``Tag`` an object is associated with. -* ``content_type`` -- The ``ContentType`` of the associated model - instance. -* ``object_id`` -- The id of the associated object. -* ``object`` -- The associated object itself, accessible via the - Generic Relations API. - -Manager functions -~~~~~~~~~~~~~~~~~ - -The ``TaggedItem`` model has a custom manager which has the following -helper methods, which accept either a ``QuerySet`` or a ``Model`` -class as one of their arguments. To restrict the objects which are -returned, pass in a filtered ``QuerySet`` for this argument: - -* ``get_by_model(queryset_or_model, tag)`` -- creates a ``QuerySet`` - containing instances of the specififed model which are tagged with - the given tag or tags. - -* ``get_intersection_by_model(queryset_or_model, tags)`` -- creates a - ``QuerySet`` containing instances of the specified model which are - tagged with every tag in a list of tags. - - ``get_by_model`` will call this function behind the scenes when you - pass it a list, so you can use ``get_by_model`` instead of calling - this method directly. - -* ``get_union_by_model(queryset_or_model, tags)`` -- creates a - ``QuerySet`` containing instances of the specified model which are - tagged with any tag in a list of tags. - -.. _`get_related method`: - -* ``get_related(obj, queryset_or_model, num=None)`` - returns a list of - instances of the specified model which share tags with the model - instance ``obj``, ordered by the number of shared tags in descending - order. - - If ``num`` is given, a maximum of ``num`` instances will be returned. - -Basic usage ------------ - -Retrieving tagged objects -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Objects may be retrieved based on their tags using the ``get_by_model`` -manager method:: - - >>> from shop.apps.products.models import Widget - >>> from tagging.models import Tag - >>> house_tag = Tag.objects.get(name='house') - >>> TaggedItem.objects.get_by_model(Widget, house_tag) - [<Widget: pk=1>, <Widget: pk=2>] - -Passing a list of tags to ``get_by_model`` returns an intersection of -objects which have those tags, i.e. tag1 AND tag2 ... AND tagN:: - - >>> thing_tag = Tag.objects.get(name='thing') - >>> TaggedItem.objects.get_by_model(Widget, [house_tag, thing_tag]) - [<Widget: pk=1>] - -Functions which take tags are flexible when it comes to tag input:: - - >>> TaggedItem.objects.get_by_model(Widget, Tag.objects.filter(name__in=['house', 'thing'])) - [<Widget: pk=1>] - >>> TaggedItem.objects.get_by_model(Widget, 'house thing') - [<Widget: pk=1>] - >>> TaggedItem.objects.get_by_model(Widget, ['house', 'thing']) - [<Widget: pk=1>] - -Restricting objects returned -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pass in a ``QuerySet`` to restrict the objects returned:: - - # Retrieve all Widgets which have a price less than 50, tagged with 'house' - TaggedItem.objects.get_by_model(Widget.objects.filter(price__lt=50), 'house') - - # Retrieve all Widgets which have a name starting with 'a', tagged with any - # of 'house', 'garden' or 'water'. - TaggedItem.objects.get_union_by_model(Widget.objects.filter(name__startswith='a'), - ['house', 'garden', 'water']) - - -Utilities -========= - -Tag-related utility functions are defined in the ``tagging.utils`` -module: - -``parse_tag_input(input)`` --------------------------- - -Parses tag input, with multiple word input being activated and -delineated by commas and double quotes. Quotes take precedence, so they -may contain commas. - -Returns a sorted list of unique tag names. - -See `tag input`_ for more details. - -``edit_string_for_tags(tags)`` ------------------------------- -Given list of ``Tag`` instances, creates a string representation of the -list suitable for editing by the user, such that submitting the given -string representation back without changing it will give the same list -of tags. - -Tag names which contain commas will be double quoted. - -If any tag name which isn't being quoted contains whitespace, the -resulting string of tag names will be comma-delimited, otherwise it will -be space-delimited. - -``get_tag_list(tags)`` ----------------------- - -Utility function for accepting tag input in a flexible manner. - -If a ``Tag`` object is given, it will be returned in a list as its -single occupant. - -If given, the tag names in the following will be used to create a -``Tag`` ``QuerySet``: - - * A string, which may contain multiple tag names. - * A list or tuple of strings corresponding to tag names. - * A list or tuple of integers corresponding to tag ids. - -If given, the following will be returned as-is: - - * A list or tuple of ``Tag`` objects. - * A ``Tag`` ``QuerySet``. - -``calculate_cloud(tags, steps=4, distribution=tagging.utils.LOGARITHMIC)`` --------------------------------------------------------------------------- - -Add a ``font_size`` attribute to each tag according to the frequency of -its use, as indicated by its ``count`` attribute. - -``steps`` defines the range of font sizes - ``font_size`` will be an -integer between 1 and ``steps`` (inclusive). - -``distribution`` defines the type of font size distribution algorithm -which will be used - logarithmic or linear. It must be one of -``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``. - - -Model Fields -============ - -The ``tagging.fields`` module contains fields which make it easy to -integrate tagging into your models and into the -``django.contrib.admin`` application. - -Field types ------------ - -``TagField`` -~~~~~~~~~~~~ - -A ``CharField`` that actually works as a relationship to tags "under -the hood". - -Using this example model:: - - class Link(models.Model): - ... - tags = TagField() - -Setting tags:: - - >>> l = Link.objects.get(...) - >>> l.tags = 'tag1 tag2 tag3' - -Getting tags for an instance:: - - >>> l.tags - 'tag1 tag2 tag3' - -Getting tags for a model - i.e. all tags used by all instances of the -model:: - - >>> Link.tags - 'tag1 tag2 tag3 tag4 tag5' - -This field will also validate that it has been given a valid list of -tag names, separated by a single comma, a single space or a comma -followed by a space, using the ``isTagList`` validator from -``tagging.validators``. - - -Form fields -=========== - -The ``tagging.forms`` module contains a ``Field`` for use with -Django's `newforms library`_ which takes care of validating tag name -input when used in your forms. - -.. _`newforms library`: http://www.djangoproject.com/documentation/newforms/ - -Field types ------------ - -``TagField`` -~~~~~~~~~~~~ - -A form ``Field`` which is displayed as a single-line text input, which -validates that the input it receives is a valid list of tag names. - -When you generate a form for one of your models automatically, using -the ``ModelForm`` class provided by newforms, any -``tagging.fields.TagField`` fields in your model will automatically be -represented by a ``tagging.forms.TagField`` in the generated form. - - -Generic views -============= - -The ``tagging.views`` module contains views to handle simple cases of -common display logic related to tagging. - -``tagging.views.tagged_object_list`` ------------------------------------- - -**Description:** - -A view that displays a list of objects for a given model which have a -given tag. This is a thin wrapper around the -``django.views.generic.list_detail.object_list`` view, which takes a -model and a tag as its arguments (in addition to the other optional -arguments supported by ``object_list``), building the appropriate -``QuerySet`` for you instead of expecting one to be passed in. - -**Required arguments:** - - * ``queryset_or_model``: A ``QuerySet`` or Django model class for the - object which will be listed. - - * ``tag``: The tag which objects of the given model must have in - order to be listed. - -**Optional arguments:** - -Please refer to the `object_list documentation`_ for additional optional -arguments which may be given. - - * ``related_tags``: If ``True``, a ``related_tags`` context variable - will also contain tags related to the given tag for the given - model. - - * ``related_tag_counts``: If ``True`` and ``related_tags`` is - ``True``, each related tag will have a ``count`` attribute - indicating the number of items which have it in addition to the - given tag. - -**Template context:** - -Please refer to the `object_list documentation`_ for additional -template context variables which may be provided. - - * ``tag``: The ``Tag`` instance for the given tag. - -.. _`object_list documentation`: http://www.djangoproject.com/documentation/generic_views/#django-views-generic-list-detail-object-list - -Example usage -~~~~~~~~~~~~~ - -The following sample URLconf demonstrates using this generic view to -list items of a particular model class which have a given tag:: - - from django.conf.urls.defaults import * - - from tagging.views import tagged_object_list - - from shop.apps.products.models import Widget - - urlpatterns = patterns('', - url(r'^widgets/tag/(?P<tag>[^/]+)/$', - tagged_object_list, - dict(model=Widget, paginate_by=10, allow_empty=True, - template_object_name='widget'), - name='widget_tag_detail'), - ) - -The following sample view demonstrates wrapping this generic view to -perform filtering of the objects which are listed:: - - from myapp.models import People - - from tagging.views import tagged_object_list - - def tagged_people(request, country_code, tag): - queryset = People.objects.filter(country__code=country_code) - return tagged_object_list(request, queryset, tag, paginate_by=25, - allow_empty=True, template_object_name='people') - - -Template tags -============= - -The ``tagging.templatetags.tagging_tags`` module defines a number of -template tags which may be used to work with tags. - -Tag reference -------------- - -tags_for_model -~~~~~~~~~~~~~~ - -Retrieves a list of ``Tag`` objects associated with a given model and -stores them in a context variable. - -Usage:: - - {% tags_for_model [model] as [varname] %} - -The model is specified in ``[appname].[modelname]`` format. - -Extended usage:: - - {% tags_for_model [model] as [varname] with counts %} - -If specified - by providing extra ``with counts`` arguments - adds a -``count`` attribute to each tag containing the number of instances of -the given model which have been tagged with it. - -Examples:: - - {% tags_for_model products.Widget as widget_tags %} - {% tags_for_model products.Widget as widget_tags with counts %} - -tag_cloud_for_model -~~~~~~~~~~~~~~~~~~~ - -Retrieves a list of ``Tag`` objects for a given model, with tag cloud -attributes set, and stores them in a context variable. - -Usage:: - - {% tag_cloud_for_model [model] as [varname] %} - -The model is specified in ``[appname].[modelname]`` format. - -Extended usage:: - - {% tag_cloud_for_model [model] as [varname] with [options] %} - -Extra options can be provided after an optional ``with`` argument, with -each option being specified in ``[name]=[value]`` format. Valid extra -options are: - - ``steps`` - Integer. Defines the range of font sizes. - - ``min_count`` - Integer. Defines the minimum number of times a tag must have - been used to appear in the cloud. - - ``distribution`` - One of ``linear`` or ``log``. Defines the font-size - distribution algorithm to use when generating the tag cloud. - -Examples:: - - {% tag_cloud_for_model products.Widget as widget_tags %} - {% tag_cloud_for_model products.Widget as widget_tags with steps=9 min_count=3 distribution=log %} - -tags_for_object -~~~~~~~~~~~~~~~ - -Retrieves a list of ``Tag`` objects associated with an object and stores -them in a context variable. - -Usage:: - - {% tags_for_object [object] as [varname] %} - -Example:: - - {% tags_for_object foo_object as tag_list %} - -tagged_objects -~~~~~~~~~~~~~~ - -Retrieves a list of instances of a given model which are tagged with a -given ``Tag`` and stores them in a context variable. - -Usage:: - - {% tagged_objects [tag] in [model] as [varname] %} - -The model is specified in ``[appname].[modelname]`` format. - -The tag must be an instance of a ``Tag``, not the name of a tag. - -Example:: - - {% tagged_objects comedy_tag in tv.Show as comedies %} diff --git a/docs/overview.txt b/docs/overview.txt index 9641f03..8b65075 100644 --- a/docs/overview.txt +++ b/docs/overview.txt @@ -76,7 +76,7 @@ the command ``svn update`` from within the ``tagging-trunk`` directory. copy of the source code. .. _`Subversion`: http://subversion.tigris.org -.. _`PYTHONPATH`: http://docs.python.org/tut/node8.html#SECTION008110000000000000000 +.. _`PYTHONPATH`: http://www.python.org/doc/2.5.2/tut/node8.html#SECTION008120000000000000000 .. _`junction`: http://www.microsoft.com/technet/sysinternals/FileAndDisk/Junction.mspx .. _`CHANGELOG`: http://django-tagging.googlecode.com/svn/trunk/CHANGELOG.txt .. _`backwards-incompatible changes wiki page`: http://code.google.com/p/django-tagging/wiki/BackwardsIncompatibleChanges @@ -119,8 +119,7 @@ Default: ``50`` An integer which specifies the maximum length which any tag is allowed to have. This is used for validation in the ``django.contrib.admin`` -application and in any ``newforms`` forms automatically generated using -``ModelForm``. +application and in any forms automatically generated using ``ModelForm``. Registering your models @@ -402,7 +401,7 @@ function:: [<Tag: house>, <Tag: thing>] Tags are created, associated and unassociated accordingly when you use -``update_tags`` and ``add_tags``:: +``update_tags`` and ``add_tag``:: >>> Tag.objects.update_tags(widget, 'house monkey') >>> Tag.objects.get_for_object(widget) @@ -453,7 +452,7 @@ greater than 99:: >>> Tag.objects.usage_for_model(Widget, filters=dict(size__gt=99, user__username='Alan')) -.. _`field lookups`: http://www.djangoproject.com/documentation/db-api/#field-lookups +.. _`field lookups`: http://docs.djangoproject.com/en/dev/topics/db/queries/#field-lookups **New in development version** @@ -699,18 +698,17 @@ model:: This field will also validate that it has been given a valid list of tag names, separated by a single comma, a single space or a comma -followed by a space, using the ``isTagList`` validator from -``tagging.validators``. +followed by a space. Form fields =========== The ``tagging.forms`` module contains a ``Field`` for use with -Django's `newforms library`_ which takes care of validating tag name +Django's `forms library`_ which takes care of validating tag name input when used in your forms. -.. _`newforms library`: http://www.djangoproject.com/documentation/newforms/ +.. _`forms library`: http://docs.djangoproject.com/en/dev/topics/forms/ Field types ----------- @@ -722,9 +720,9 @@ A form ``Field`` which is displayed as a single-line text input, which validates that the input it receives is a valid list of tag names. When you generate a form for one of your models automatically, using -the ``ModelForm`` class provided by newforms, any -``tagging.fields.TagField`` fields in your model will automatically be -represented by a ``tagging.forms.TagField`` in the generated form. +the ``ModelForm`` class, any ``tagging.fields.TagField`` fields in your +model will automatically be represented by a ``tagging.forms.TagField`` +in the generated form. Generic views @@ -774,7 +772,7 @@ template context variables which may be provided. * ``tag``: The ``Tag`` instance for the given tag. -.. _`object_list documentation`: http://www.djangoproject.com/documentation/generic_views/#django-views-generic-list-detail-object-list +.. _`object_list documentation`: http://docs.djangoproject.com/en/dev/ref/generic-views/#django-views-generic-list-detail-object-list Example usage ~~~~~~~~~~~~~ @@ -791,7 +789,7 @@ list items of a particular model class which have a given tag:: urlpatterns = patterns('', url(r'^widgets/tag/(?P<tag>[^/]+)/$', tagged_object_list, - dict(model=Widget, paginate_by=10, allow_empty=True, + dict(queryset_or_model=Widget, paginate_by=10, allow_empty=True, template_object_name='widget'), name='widget_tag_detail'), ) @@ -46,7 +46,7 @@ for dirpath, dirnames, filenames in os.walk(tagging_dir): data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) # Dynamically calculate the version based on tagging.VERSION -version_tuple = __import__('tagging').VERSION +version_tuple = (0, 3, 'pre') if version_tuple[2] is not None: version = "%d.%d_%s" % version_tuple else: diff --git a/tagging/.svn/all-wcprops b/tagging/.svn/all-wcprops deleted file mode 100644 index fbf957a..0000000 --- a/tagging/.svn/all-wcprops +++ /dev/null @@ -1,65 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 31 -/svn/!svn/ver/147/trunk/tagging -END -validators.py -K 25 -svn:wc:ra_dav:version-url -V 45 -/svn/!svn/ver/114/trunk/tagging/validators.py -END -generic.py -K 25 -svn:wc:ra_dav:version-url -V 42 -/svn/!svn/ver/138/trunk/tagging/generic.py -END -managers.py -K 25 -svn:wc:ra_dav:version-url -V 43 -/svn/!svn/ver/143/trunk/tagging/managers.py -END -views.py -K 25 -svn:wc:ra_dav:version-url -V 40 -/svn/!svn/ver/130/trunk/tagging/views.py -END -__init__.py -K 25 -svn:wc:ra_dav:version-url -V 43 -/svn/!svn/ver/130/trunk/tagging/__init__.py -END -utils.py -K 25 -svn:wc:ra_dav:version-url -V 40 -/svn/!svn/ver/147/trunk/tagging/utils.py -END -settings.py -K 25 -svn:wc:ra_dav:version-url -V 43 -/svn/!svn/ver/114/trunk/tagging/settings.py -END -models.py -K 25 -svn:wc:ra_dav:version-url -V 41 -/svn/!svn/ver/147/trunk/tagging/models.py -END -fields.py -K 25 -svn:wc:ra_dav:version-url -V 41 -/svn/!svn/ver/141/trunk/tagging/fields.py -END -forms.py -K 25 -svn:wc:ra_dav:version-url -V 40 -/svn/!svn/ver/141/trunk/tagging/forms.py -END diff --git a/tagging/.svn/dir-prop-base b/tagging/.svn/dir-prop-base deleted file mode 100644 index 4cc643b..0000000 --- a/tagging/.svn/dir-prop-base +++ /dev/null @@ -1,6 +0,0 @@ -K 10 -svn:ignore -V 6 -*.pyc - -END diff --git a/tagging/.svn/entries b/tagging/.svn/entries deleted file mode 100644 index e226b2d..0000000 --- a/tagging/.svn/entries +++ /dev/null @@ -1,374 +0,0 @@ -9 - -dir -147 -http://django-tagging.googlecode.com/svn/trunk/tagging -http://django-tagging.googlecode.com/svn - - - -2008-08-20T23:28:50.903432Z -147 -doug.napoleone -has-props - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -83e7428b-ec2a-0410-86f2-bf466d0e5e72 - -validators.py -file - - - - -2008-08-21T18:05:41.000000Z -f94f883336937e8d8f9fb2dd0f943a54 -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -1126 - -tests -dir - -generic.py -file - - - - -2008-08-21T18:05:41.000000Z -18f6e0f760b377326ea81124dcf47db1 -2008-06-24T09:19:57.126367Z -138 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -1931 - -managers.py -file - - - - -2008-08-21T18:05:41.000000Z -97fce4fc5814e3d5db5a9f89a6729bb4 -2008-08-15T05:59:12.272414Z -143 -doug.napoleone -has-props - - - - - - - - - - - - - - - - - - - - -2332 - -views.py -file - - - - -2008-08-21T18:05:41.000000Z -90a5836ed3edc70604f63fc26e09ab48 -2008-01-21T17:55:31.741058Z -130 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -2118 - -__init__.py -file - - - - -2008-08-21T18:05:41.000000Z -a52a98abdba2556f7eff853a2fae04a3 -2008-01-21T17:55:31.741058Z -130 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -855 - -utils.py -file - - - - -2008-08-21T18:05:41.000000Z -e7e102dc295758733e391e64ab58521d -2008-08-20T23:28:50.903432Z -147 -doug.napoleone -has-props - - - - - - - - - - - - - - - - - - - - -8630 - -settings.py -file - - - - -2008-08-21T18:05:41.000000Z -0dd9381613e65273a53c05da4c901219 -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -467 - -templatetags -dir - -models.py -file - - - - -2008-08-21T18:05:41.000000Z -24f36128b354a3a765b10ff2b14269d8 -2008-08-20T23:28:50.903432Z -147 -doug.napoleone -has-props - - - - - - - - - - - - - - - - - - - - -20350 - -fields.py -file - - - - -2008-08-21T18:05:41.000000Z -0496278c34f7f3130b0f1e4b7f6889ff -2008-08-11T19:04:50.317273Z -141 -doug.napoleone -has-props - - - - - - - - - - - - - - - - - - - - -3664 - -forms.py -file - - - - -2008-08-21T18:05:41.000000Z -f9725336959cda1e8861e8240b6e4cd5 -2008-08-11T19:04:50.317273Z -141 -doug.napoleone -has-props - - - - - - - - - - - - - - - - - - - - -750 - diff --git a/tagging/.svn/format b/tagging/.svn/format deleted file mode 100644 index ec63514..0000000 --- a/tagging/.svn/format +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/tagging/.svn/prop-base/__init__.py.svn-base b/tagging/.svn/prop-base/__init__.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/__init__.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/fields.py.svn-base b/tagging/.svn/prop-base/fields.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/fields.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/forms.py.svn-base b/tagging/.svn/prop-base/forms.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/forms.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/generic.py.svn-base b/tagging/.svn/prop-base/generic.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/generic.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/managers.py.svn-base b/tagging/.svn/prop-base/managers.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/managers.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/models.py.svn-base b/tagging/.svn/prop-base/models.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/models.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/settings.py.svn-base b/tagging/.svn/prop-base/settings.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/settings.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/utils.py.svn-base b/tagging/.svn/prop-base/utils.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/utils.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/validators.py.svn-base b/tagging/.svn/prop-base/validators.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/validators.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/prop-base/views.py.svn-base b/tagging/.svn/prop-base/views.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/.svn/prop-base/views.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/.svn/text-base/__init__.py.svn-base b/tagging/.svn/text-base/__init__.py.svn-base deleted file mode 100644 index 9241c20..0000000 --- a/tagging/.svn/text-base/__init__.py.svn-base +++ /dev/null @@ -1,30 +0,0 @@ -from django.utils.translation import ugettext as _ - -from tagging.managers import ModelTaggedItemManager, TagDescriptor - -VERSION = (0, 3, 'pre') - -class AlreadyRegistered(Exception): - """ - An attempt was made to register a model more than once. - """ - pass - -registry = [] - -def register(model, tag_descriptor_attr='tags', - tagged_item_manager_attr='tagged'): - """ - Sets the given model class up for working with tags. - """ - if model in registry: - raise AlreadyRegistered( - _('The model %s has already been registered.') % model.__name__) - registry.append(model) - - # Add tag descriptor - setattr(model, tag_descriptor_attr, TagDescriptor()) - - # Add custom manager - ModelTaggedItemManager().contribute_to_class(model, - tagged_item_manager_attr) diff --git a/tagging/.svn/text-base/fields.py.svn-base b/tagging/.svn/text-base/fields.py.svn-base deleted file mode 100644 index 5b39e84..0000000 --- a/tagging/.svn/text-base/fields.py.svn-base +++ /dev/null @@ -1,109 +0,0 @@ -""" -A custom Model Field for tagging. -""" -from django.db.models import signals -from django.db.models.fields import CharField -from django.utils.translation import ugettext_lazy as _ - -from tagging import settings -from tagging.models import Tag -from tagging.utils import edit_string_for_tags -from tagging.validators import isTagList - -class TagField(CharField): - """ - A "special" character field that actually works as a relationship to tags - "under the hood". This exposes a space-separated string of tags, but does - the splitting/reordering/etc. under the hood. - """ - def __init__(self, *args, **kwargs): - kwargs['max_length'] = kwargs.get('max_length', 255) - kwargs['blank'] = kwargs.get('blank', True) - kwargs['validator_list'] = [isTagList] + kwargs.get('validator_list', []) - super(TagField, self).__init__(*args, **kwargs) - - def contribute_to_class(self, cls, name): - super(TagField, self).contribute_to_class(cls, name) - - # Make this object the descriptor for field access. - setattr(cls, self.name, self) - - # Save tags back to the database post-save - signals.post_save.connect(self._save, cls, True) - - def __get__(self, instance, owner=None): - """ - Tag getter. Returns an instance's tags if accessed on an instance, and - all of a model's tags if called on a class. That is, this model:: - - class Link(models.Model): - ... - tags = TagField() - - Lets you do both of these:: - - >>> l = Link.objects.get(...) - >>> l.tags - 'tag1 tag2 tag3' - - >>> Link.tags - 'tag1 tag2 tag3 tag4' - - """ - # Handle access on the model (i.e. Link.tags) - if instance is None: - return edit_string_for_tags(Tag.objects.usage_for_model(owner)) - - tags = self._get_instance_tag_cache(instance) - if tags is None: - if instance.pk is None: - self._set_instance_tag_cache(instance, '') - else: - self._set_instance_tag_cache( - instance, edit_string_for_tags(Tag.objects.get_for_object(instance))) - return self._get_instance_tag_cache(instance) - - def __set__(self, instance, value): - """ - Set an object's tags. - """ - if instance is None: - raise AttributeError(_('%s can only be set on instances.') % self.name) - if settings.FORCE_LOWERCASE_TAGS and value is not None: - value = value.lower() - self._set_instance_tag_cache(instance, value) - - def _save(self, **kwargs): #signal, sender, instance): - """ - Save tags back to the database - """ - tags = self._get_instance_tag_cache(kwargs['instance']) - if tags is not None: - Tag.objects.update_tags(kwargs['instance'], tags) - - def __delete__(self, instance): - """ - Clear all of an object's tags. - """ - self._set_instance_tag_cache(instance, '') - - def _get_instance_tag_cache(self, instance): - """ - Helper: get an instance's tag cache. - """ - return getattr(instance, '_%s_cache' % self.attname, None) - - def _set_instance_tag_cache(self, instance, tags): - """ - Helper: set an instance's tag cache. - """ - setattr(instance, '_%s_cache' % self.attname, tags) - - def get_internal_type(self): - return 'CharField' - - def formfield(self, **kwargs): - from tagging import forms - defaults = {'form_class': forms.TagField} - defaults.update(kwargs) - return super(TagField, self).formfield(**defaults) diff --git a/tagging/.svn/text-base/forms.py.svn-base b/tagging/.svn/text-base/forms.py.svn-base deleted file mode 100644 index e65aec4..0000000 --- a/tagging/.svn/text-base/forms.py.svn-base +++ /dev/null @@ -1,23 +0,0 @@ -""" -Tagging components for Django's ``newforms`` form library. -""" -from django import forms -from django.utils.translation import ugettext as _ - -from tagging import settings -from tagging.utils import parse_tag_input - -class TagField(forms.CharField): - """ - A ``CharField`` which validates that its input is a valid list of - tag names. - """ - def clean(self, value): - value = super(TagField, self).clean(value) - if value == u'': - return value - for tag_name in parse_tag_input(value): - if len(tag_name) > settings.MAX_TAG_LENGTH: - raise forms.ValidationError( - _('Each tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) - return value diff --git a/tagging/.svn/text-base/generic.py.svn-base b/tagging/.svn/text-base/generic.py.svn-base deleted file mode 100644 index 75d1b8e..0000000 --- a/tagging/.svn/text-base/generic.py.svn-base +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib.contenttypes.models import ContentType - -def fetch_content_objects(tagged_items, select_related_for=None): - """ - Retrieves ``ContentType`` and content objects for the given list of - ``TaggedItems``, grouping the retrieval of content objects by model - type to reduce the number of queries executed. - - This results in ``number_of_content_types + 1`` queries rather than - the ``number_of_tagged_items * 2`` queries you'd get by iterating - over the list and accessing each item's ``object`` attribute. - - A ``select_related_for`` argument can be used to specify a list of - of model names (corresponding to the ``model`` field of a - ``ContentType``) for which ``select_related`` should be used when - retrieving model instances. - """ - if select_related_for is None: select_related_for = [] - - # Group content object pks by their content type pks - objects = {} - for item in tagged_items: - objects.setdefault(item.content_type_id, []).append(item.object_id) - - # Retrieve content types and content objects in bulk - content_types = ContentType._default_manager.in_bulk(objects.keys()) - for content_type_pk, object_pks in objects.iteritems(): - model = content_types[content_type_pk].model_class() - if content_types[content_type_pk].model in select_related_for: - objects[content_type_pk] = model._default_manager.select_related().in_bulk(object_pks) - else: - objects[content_type_pk] = model._default_manager.in_bulk(object_pks) - - # Set content types and content objects in the appropriate cache - # attributes, so accessing the 'content_type' and 'object' - # attributes on each tagged item won't result in further database - # hits. - for item in tagged_items: - item._object_cache = objects[item.content_type_id][item.object_id] - item._content_type_cache = content_types[item.content_type_id] diff --git a/tagging/.svn/text-base/managers.py.svn-base b/tagging/.svn/text-base/managers.py.svn-base deleted file mode 100644 index 51e2473..0000000 --- a/tagging/.svn/text-base/managers.py.svn-base +++ /dev/null @@ -1,68 +0,0 @@ -""" -Custom managers for Django models registered with the tagging -application. -""" -from django.contrib.contenttypes.models import ContentType -from django.db import models - -from tagging.models import Tag, TaggedItem - -class ModelTagManager(models.Manager): - """ - A manager for retrieving tags for a particular model. - """ - def get_query_set(self): - ctype = ContentType.objects.get_for_model(self.model) - return Tag.objects.filter( - items__content_type__pk=ctype.pk).distinct() - - def cloud(self, *args, **kwargs): - return Tag.objects.cloud_for_model(self.model, *args, **kwargs) - - def related(self, tags, *args, **kwargs): - return Tag.objects.related_for_model(tags, self.model, *args, **kwargs) - - def usage(self, *args, **kwargs): - return Tag.objects.usage_for_model(self.model, *arg, **kwargs) - -class ModelTaggedItemManager(models.Manager): - """ - A manager for retrieving model instances based on their tags. - """ - def related_to(self, obj, queryset=None, num=None): - if queryset is None: - return TaggedItem.objects.get_related(obj, self.model, num=num) - else: - return TaggedItem.objects.get_related(obj, queryset, num=num) - - def with_all(self, tags, queryset=None): - if queryset is None: - return TaggedItem.objects.get_by_model(self.model, tags) - else: - return TaggedItem.objects.get_by_model(queryset, tags) - - def with_any(self, tags, queryset=None): - if queryset is None: - return TaggedItem.objects.get_union_by_model(self.model, tags) - else: - return TaggedItem.objects.get_union_by_model(queryset, tags) - -class TagDescriptor(object): - """ - A descriptor which provides access to a ``ModelTagManager`` for - model classes and simple retrieval, updating and deletion of tags - for model instances. - """ - def __get__(self, instance, owner): - if not instance: - tag_manager = ModelTagManager() - tag_manager.model = owner - return tag_manager - else: - return Tag.objects.get_for_object(instance) - - def __set__(self, instance, value): - Tag.objects.update_tags(instance, value) - - def __delete__(self, instance): - Tag.objects.update_tags(instance, None) diff --git a/tagging/.svn/text-base/models.py.svn-base b/tagging/.svn/text-base/models.py.svn-base deleted file mode 100644 index 7463ddb..0000000 --- a/tagging/.svn/text-base/models.py.svn-base +++ /dev/null @@ -1,502 +0,0 @@ -""" -Models and managers for generic tagging. -""" -# Python 2.3 compatibility -try: - set -except NameError: - from sets import Set as set - -from django.contrib.contenttypes import generic -from django.contrib.contenttypes.models import ContentType -from django.db import connection, models -from django.db.models.query import QuerySet -from django.utils.translation import ugettext_lazy as _ - -from tagging import settings -from tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input -from tagging.utils import LOGARITHMIC -from tagging.validators import isTag - -qn = connection.ops.quote_name - -try: - from django.db.models.query import parse_lookup -except ImportError: - parse_lookup = None - -############ -# Managers # -############ - -class TagManager(models.Manager): - def update_tags(self, obj, tag_names): - """ - Update tags associated with an object. - """ - ctype = ContentType.objects.get_for_model(obj) - current_tags = list(self.filter(items__content_type__pk=ctype.pk, - items__object_id=obj.pk)) - updated_tag_names = parse_tag_input(tag_names) - if settings.FORCE_LOWERCASE_TAGS: - updated_tag_names = [t.lower() for t in updated_tag_names] - - # Remove tags which no longer apply - tags_for_removal = [tag for tag in current_tags \ - if tag.name not in updated_tag_names] - if len(tags_for_removal): - TaggedItem._default_manager.filter(content_type__pk=ctype.pk, - object_id=obj.pk, - tag__in=tags_for_removal).delete() - # Add new tags - current_tag_names = [tag.name for tag in current_tags] - for tag_name in updated_tag_names: - if tag_name not in current_tag_names: - tag, created = self.get_or_create(name=tag_name) - TaggedItem._default_manager.create(tag=tag, object=obj) - - def add_tag(self, obj, tag_name): - """ - Associates the given object with a tag. - """ - tag_names = parse_tag_input(tag_name) - if not len(tag_names): - raise AttributeError(_('No tags were given: "%s".') % tag_name) - if len(tag_names) > 1: - raise AttributeError(_('Multiple tags were given: "%s".') % tag_name) - tag_name = tag_names[0] - if settings.FORCE_LOWERCASE_TAGS: - tag_name = tag_name.lower() - tag, created = self.get_or_create(name=tag_name) - ctype = ContentType.objects.get_for_model(obj) - TaggedItem._default_manager.get_or_create( - tag=tag, content_type=ctype, object_id=obj.pk) - - def get_for_object(self, obj): - """ - Create a queryset matching all tags associated with the given - object. - """ - ctype = ContentType.objects.get_for_model(obj) - return self.filter(items__content_type__pk=ctype.pk, - items__object_id=obj.pk) - - def _get_usage(self, model, counts=False, min_count=None, extra_joins=None, extra_criteria=None, params=None): - """ - Perform the custom SQL query for ``usage_for_model`` and - ``usage_for_queryset``. - """ - if min_count is not None: counts = True - - model_table = qn(model._meta.db_table) - model_pk = '%s.%s' % (model_table, qn(model._meta.pk.column)) - query = """ - SELECT DISTINCT %(tag)s.id, %(tag)s.name%(count_sql)s - FROM - %(tag)s - INNER JOIN %(tagged_item)s - ON %(tag)s.id = %(tagged_item)s.tag_id - INNER JOIN %(model)s - ON %(tagged_item)s.object_id = %(model_pk)s - %%s - WHERE %(tagged_item)s.content_type_id = %(content_type_id)s - %%s - GROUP BY %(tag)s.id, %(tag)s.name - %%s - ORDER BY %(tag)s.name ASC""" % { - 'tag': qn(self.model._meta.db_table), - 'count_sql': counts and (', COUNT(%s)' % model_pk) or '', - 'tagged_item': qn(TaggedItem._meta.db_table), - 'model': model_table, - 'model_pk': model_pk, - 'content_type_id': ContentType.objects.get_for_model(model).pk, - } - - min_count_sql = '' - if min_count is not None: - min_count_sql = 'HAVING COUNT(%s) >= %%s' % model_pk - params.append(min_count) - - cursor = connection.cursor() - cursor.execute(query % (extra_joins, extra_criteria, min_count_sql), params) - tags = [] - for row in cursor.fetchall(): - t = self.model(*row[:2]) - if counts: - t.count = row[2] - tags.append(t) - return tags - - def usage_for_model(self, model, counts=False, min_count=None, filters=None): - """ - Obtain a list of tags associated with instances of the given - Model class. - - If ``counts`` is True, a ``count`` attribute will be added to - each tag, indicating how many times it has been used against - the Model class in question. - - If ``min_count`` is given, only tags which have a ``count`` - greater than or equal to ``min_count`` will be returned. - Passing a value for ``min_count`` implies ``counts=True``. - - To limit the tags (and counts, if specified) returned to those - used by a subset of the Model's instances, pass a dictionary - of field lookups to be applied to the given Model as the - ``filters`` argument. - """ - if filters is None: filters = {} - - if not parse_lookup: - # post-queryset-refactor (hand off to usage_for_queryset) - queryset = model._default_manager.filter() - for f in filters.items(): - queryset.query.add_filter(f) - usage = self.usage_for_queryset(queryset, counts, min_count) - else: - # pre-queryset-refactor - extra_joins = '' - extra_criteria = '' - params = [] - if len(filters) > 0: - joins, where, params = parse_lookup(filters.items(), model._meta) - extra_joins = ' '.join(['%s %s AS %s ON %s' % (join_type, table, alias, condition) - for (alias, (table, join_type, condition)) in joins.items()]) - extra_criteria = 'AND %s' % (' AND '.join(where)) - usage = self._get_usage(model, counts, min_count, extra_joins, extra_criteria, params) - - return usage - - def usage_for_queryset(self, queryset, counts=False, min_count=None): - """ - Obtain a list of tags associated with instances of a model - contained in the given queryset. - - If ``counts`` is True, a ``count`` attribute will be added to - each tag, indicating how many times it has been used against - the Model class in question. - - If ``min_count`` is given, only tags which have a ``count`` - greater than or equal to ``min_count`` will be returned. - Passing a value for ``min_count`` implies ``counts=True``. - """ - if parse_lookup: - raise AttributeError("'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.") - - extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:]) - where, params = queryset.query.where.as_sql() - if where: - extra_criteria = 'AND %s' % where - else: - extra_criteria = '' - return self._get_usage(queryset.model, counts, min_count, extra_joins, extra_criteria, params) - - def related_for_model(self, tags, model, counts=False, min_count=None): - """ - Obtain a list of tags related to a given list of tags - that - is, other tags used by items which have all the given tags. - - If ``counts`` is True, a ``count`` attribute will be added to - each tag, indicating the number of items which have it in - addition to the given list of tags. - - If ``min_count`` is given, only tags which have a ``count`` - greater than or equal to ``min_count`` will be returned. - Passing a value for ``min_count`` implies ``counts=True``. - """ - if min_count is not None: counts = True - tags = get_tag_list(tags) - tag_count = len(tags) - tagged_item_table = qn(TaggedItem._meta.db_table) - query = """ - SELECT %(tag)s.id, %(tag)s.name%(count_sql)s - FROM %(tagged_item)s INNER JOIN %(tag)s ON %(tagged_item)s.tag_id = %(tag)s.id - WHERE %(tagged_item)s.content_type_id = %(content_type_id)s - AND %(tagged_item)s.object_id IN - ( - SELECT %(tagged_item)s.object_id - FROM %(tagged_item)s, %(tag)s - WHERE %(tagged_item)s.content_type_id = %(content_type_id)s - AND %(tag)s.id = %(tagged_item)s.tag_id - AND %(tag)s.id IN (%(tag_id_placeholders)s) - GROUP BY %(tagged_item)s.object_id - HAVING COUNT(%(tagged_item)s.object_id) = %(tag_count)s - ) - AND %(tag)s.id NOT IN (%(tag_id_placeholders)s) - GROUP BY %(tag)s.id, %(tag)s.name - %(min_count_sql)s - ORDER BY %(tag)s.name ASC""" % { - 'tag': qn(self.model._meta.db_table), - 'count_sql': counts and ', COUNT(%s.object_id)' % tagged_item_table or '', - 'tagged_item': tagged_item_table, - 'content_type_id': ContentType.objects.get_for_model(model).pk, - 'tag_id_placeholders': ','.join(['%s'] * tag_count), - 'tag_count': tag_count, - 'min_count_sql': min_count is not None and ('HAVING COUNT(%s.object_id) >= %%s' % tagged_item_table) or '', - } - - params = [tag.pk for tag in tags] * 2 - if min_count is not None: - params.append(min_count) - - cursor = connection.cursor() - cursor.execute(query, params) - related = [] - for row in cursor.fetchall(): - tag = self.model(*row[:2]) - if counts is True: - tag.count = row[2] - related.append(tag) - return related - - def cloud_for_model(self, model, steps=4, distribution=LOGARITHMIC, - filters=None, min_count=None): - """ - Obtain a list of tags associated with instances of the given - Model, giving each tag a ``count`` attribute indicating how - many times it has been used and a ``font_size`` attribute for - use in displaying a tag cloud. - - ``steps`` defines the range of font sizes - ``font_size`` will - be an integer between 1 and ``steps`` (inclusive). - - ``distribution`` defines the type of font size distribution - algorithm which will be used - logarithmic or linear. It must - be either ``tagging.utils.LOGARITHMIC`` or - ``tagging.utils.LINEAR``. - - To limit the tags displayed in the cloud to those associated - with a subset of the Model's instances, pass a dictionary of - field lookups to be applied to the given Model as the - ``filters`` argument. - - To limit the tags displayed in the cloud to those with a - ``count`` greater than or equal to ``min_count``, pass a value - for the ``min_count`` argument. - """ - tags = list(self.usage_for_model(model, counts=True, filters=filters, - min_count=min_count)) - return calculate_cloud(tags, steps, distribution) - -class TaggedItemManager(models.Manager): - """ - FIXME There's currently no way to get the ``GROUP BY`` and ``HAVING`` - SQL clauses required by many of this manager's methods into - Django's ORM. - - For now, we manually execute a query to retrieve the PKs of - objects we're interested in, then use the ORM's ``__in`` - lookup to return a ``QuerySet``. - - Once the queryset-refactor branch lands in trunk, this can be - tidied up significantly. - """ - def get_by_model(self, queryset_or_model, tags): - """ - Create a ``QuerySet`` containing instances of the specified - model associated with a given tag or list of tags. - """ - tags = get_tag_list(tags) - tag_count = len(tags) - if tag_count == 0: - # No existing tags were given - queryset, model = get_queryset_and_model(queryset_or_model) - return model._default_manager.none() - elif tag_count == 1: - # Optimisation for single tag - fall through to the simpler - # query below. - tag = tags[0] - else: - return self.get_intersection_by_model(queryset_or_model, tags) - - queryset, model = get_queryset_and_model(queryset_or_model) - content_type = ContentType.objects.get_for_model(model) - opts = self.model._meta - tagged_item_table = qn(opts.db_table) - return queryset.extra( - tables=[opts.db_table], - where=[ - '%s.content_type_id = %%s' % tagged_item_table, - '%s.tag_id = %%s' % tagged_item_table, - '%s.%s = %s.object_id' % (qn(model._meta.db_table), - qn(model._meta.pk.column), - tagged_item_table) - ], - params=[content_type.pk, tag.pk], - ) - - def get_intersection_by_model(self, queryset_or_model, tags): - """ - Create a ``QuerySet`` containing instances of the specified - model associated with *all* of the given list of tags. - """ - tags = get_tag_list(tags) - tag_count = len(tags) - queryset, model = get_queryset_and_model(queryset_or_model) - - if not tag_count: - return model._default_manager.none() - - model_table = qn(model._meta.db_table) - # This query selects the ids of all objects which have all the - # given tags. - query = """ - SELECT %(model_pk)s - FROM %(model)s, %(tagged_item)s - WHERE %(tagged_item)s.content_type_id = %(content_type_id)s - AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s) - AND %(model_pk)s = %(tagged_item)s.object_id - GROUP BY %(model_pk)s - HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % { - 'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)), - 'model': model_table, - 'tagged_item': qn(self.model._meta.db_table), - 'content_type_id': ContentType.objects.get_for_model(model).pk, - 'tag_id_placeholders': ','.join(['%s'] * tag_count), - 'tag_count': tag_count, - } - - cursor = connection.cursor() - cursor.execute(query, [tag.pk for tag in tags]) - object_ids = [row[0] for row in cursor.fetchall()] - if len(object_ids) > 0: - return queryset.filter(pk__in=object_ids) - else: - return model._default_manager.none() - - def get_union_by_model(self, queryset_or_model, tags): - """ - Create a ``QuerySet`` containing instances of the specified - model associated with *any* of the given list of tags. - """ - tags = get_tag_list(tags) - tag_count = len(tags) - queryset, model = get_queryset_and_model(queryset_or_model) - - if not tag_count: - return model._default_manager.none() - - model_table = qn(model._meta.db_table) - # This query selects the ids of all objects which have any of - # the given tags. - query = """ - SELECT %(model_pk)s - FROM %(model)s, %(tagged_item)s - WHERE %(tagged_item)s.content_type_id = %(content_type_id)s - AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s) - AND %(model_pk)s = %(tagged_item)s.object_id - GROUP BY %(model_pk)s""" % { - 'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)), - 'model': model_table, - 'tagged_item': qn(self.model._meta.db_table), - 'content_type_id': ContentType.objects.get_for_model(model).pk, - 'tag_id_placeholders': ','.join(['%s'] * tag_count), - } - - cursor = connection.cursor() - cursor.execute(query, [tag.pk for tag in tags]) - object_ids = [row[0] for row in cursor.fetchall()] - if len(object_ids) > 0: - return queryset.filter(pk__in=object_ids) - else: - return model._default_manager.none() - - def get_related(self, obj, queryset_or_model, num=None): - """ - Retrieve a list of instances of the specified model which share - tags with the model instance ``obj``, ordered by the number of - shared tags in descending order. - - If ``num`` is given, a maximum of ``num`` instances will be - returned. - """ - queryset, model = get_queryset_and_model(queryset_or_model) - model_table = qn(model._meta.db_table) - content_type = ContentType.objects.get_for_model(obj) - related_content_type = ContentType.objects.get_for_model(model) - query = """ - SELECT %(model_pk)s, COUNT(related_tagged_item.object_id) AS %(count)s - FROM %(model)s, %(tagged_item)s, %(tag)s, %(tagged_item)s related_tagged_item - WHERE %(tagged_item)s.object_id = %%s - AND %(tagged_item)s.content_type_id = %(content_type_id)s - AND %(tag)s.id = %(tagged_item)s.tag_id - AND related_tagged_item.content_type_id = %(related_content_type_id)s - AND related_tagged_item.tag_id = %(tagged_item)s.tag_id - AND %(model_pk)s = related_tagged_item.object_id""" - if content_type.pk == related_content_type.pk: - # Exclude the given instance itself if determining related - # instances for the same model. - query += """ - AND related_tagged_item.object_id != %(tagged_item)s.object_id""" - query += """ - GROUP BY %(model_pk)s - ORDER BY %(count)s DESC - %(limit_offset)s""" - query = query % { - 'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)), - 'count': qn('count'), - 'model': model_table, - 'tagged_item': qn(self.model._meta.db_table), - 'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table), - 'content_type_id': content_type.pk, - 'related_content_type_id': related_content_type.pk, - 'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '', - } - - cursor = connection.cursor() - cursor.execute(query, [obj.pk]) - object_ids = [row[0] for row in cursor.fetchall()] - if len(object_ids) > 0: - # Use in_bulk here instead of an id__in lookup, because id__in would - # clobber the ordering. - object_dict = queryset.in_bulk(object_ids) - return [object_dict[object_id] for object_id in object_ids \ - if object_id in object_dict] - else: - return [] - -########## -# Models # -########## - -class Tag(models.Model): - """ - A tag. - """ - name = models.CharField(_('name'), max_length=50, unique=True, db_index=True, validator_list=[isTag]) - - objects = TagManager() - - class Meta: - ordering = ('name',) - verbose_name = _('tag') - verbose_name_plural = _('tags') - - class Admin: - pass - - def __unicode__(self): - return self.name - -class TaggedItem(models.Model): - """ - Holds the relationship between a tag and the item being tagged. - """ - tag = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items') - content_type = models.ForeignKey(ContentType, verbose_name=_('content type')) - object_id = models.PositiveIntegerField(_('object id'), db_index=True) - object = generic.GenericForeignKey('content_type', 'object_id') - - objects = TaggedItemManager() - - class Meta: - # Enforce unique tag association per object - unique_together = (('tag', 'content_type', 'object_id'),) - verbose_name = _('tagged item') - verbose_name_plural = _('tagged items') - - class Admin: - pass - - def __unicode__(self): - return u'%s [%s]' % (self.object, self.tag) diff --git a/tagging/.svn/text-base/settings.py.svn-base b/tagging/.svn/text-base/settings.py.svn-base deleted file mode 100644 index 1d6224c..0000000 --- a/tagging/.svn/text-base/settings.py.svn-base +++ /dev/null @@ -1,13 +0,0 @@ -""" -Convenience module for access of custom tagging application settings, -which enforces default settings when the main settings module does not -contain the appropriate settings. -""" -from django.conf import settings - -# The maximum length of a tag's name. -MAX_TAG_LENGTH = getattr(settings, 'MAX_TAG_LENGTH', 50) - -# Whether to force all tags to lowercase before they are saved to the -# database. -FORCE_LOWERCASE_TAGS = getattr(settings, 'FORCE_LOWERCASE_TAGS', False) diff --git a/tagging/.svn/text-base/utils.py.svn-base b/tagging/.svn/text-base/utils.py.svn-base deleted file mode 100644 index e89bab0..0000000 --- a/tagging/.svn/text-base/utils.py.svn-base +++ /dev/null @@ -1,263 +0,0 @@ -""" -Tagging utilities - from user tag input parsing to tag cloud -calculation. -""" -import math -import types - -from django.db.models.query import QuerySet -from django.utils.encoding import force_unicode -from django.utils.translation import ugettext as _ - -# Python 2.3 compatibility -try: - set -except NameError: - from sets import Set as set - -def parse_tag_input(input): - """ - Parses tag input, with multiple word input being activated and - delineated by commas and double quotes. Quotes take precedence, so - they may contain commas. - - Returns a sorted list of unique tag names. - """ - if not input: - return [] - - input = force_unicode(input) - - # Special case - if there are no commas or double quotes in the - # input, we don't *do* a recall... I mean, we know we only need to - # split on spaces. - if u',' not in input and u'"' not in input: - words = list(set(split_strip(input, u' '))) - words.sort() - return words - - words = [] - buffer = [] - # Defer splitting of non-quoted sections until we know if there are - # any unquoted commas. - to_be_split = [] - saw_loose_comma = False - open_quote = False - i = iter(input) - try: - while 1: - c = i.next() - if c == u'"': - if buffer: - to_be_split.append(u''.join(buffer)) - buffer = [] - # Find the matching quote - open_quote = True - c = i.next() - while c != u'"': - buffer.append(c) - c = i.next() - if buffer: - word = u''.join(buffer).strip() - if word: - words.append(word) - buffer = [] - open_quote = False - else: - if not saw_loose_comma and c == u',': - saw_loose_comma = True - buffer.append(c) - except StopIteration: - # If we were parsing an open quote which was never closed treat - # the buffer as unquoted. - if buffer: - if open_quote and u',' in buffer: - saw_loose_comma = True - to_be_split.append(u''.join(buffer)) - if to_be_split: - if saw_loose_comma: - delimiter = u',' - else: - delimiter = u' ' - for chunk in to_be_split: - words.extend(split_strip(chunk, delimiter)) - words = list(set(words)) - words.sort() - return words - -def split_strip(input, delimiter=u','): - """ - Splits ``input`` on ``delimiter``, stripping each resulting string - and returning a list of non-empty strings. - """ - if not input: - return [] - - words = [w.strip() for w in input.split(delimiter)] - return [w for w in words if w] - -def edit_string_for_tags(tags): - """ - Given list of ``Tag`` instances, creates a string representation of - the list suitable for editing by the user, such that submitting the - given string representation back without changing it will give the - same list of tags. - - Tag names which contain commas will be double quoted. - - If any tag name which isn't being quoted contains whitespace, the - resulting string of tag names will be comma-delimited, otherwise - it will be space-delimited. - """ - names = [] - use_commas = False - for tag in tags: - name = tag.name - if u',' in name: - names.append('"%s"' % name) - continue - elif u' ' in name: - if not use_commas: - use_commas = True - names.append(name) - if use_commas: - glue = u', ' - else: - glue = u' ' - return glue.join(names) - -def get_queryset_and_model(queryset_or_model): - """ - Given a ``QuerySet`` or a ``Model``, returns a two-tuple of - (queryset, model). - - If a ``Model`` is given, the ``QuerySet`` returned will be created - using its default manager. - """ - try: - return queryset_or_model, queryset_or_model.model - except AttributeError: - return queryset_or_model._default_manager.all(), queryset_or_model - -def get_tag_list(tags): - """ - Utility function for accepting tag input in a flexible manner. - - If a ``Tag`` object is given, it will be returned in a list as - its single occupant. - - If given, the tag names in the following will be used to create a - ``Tag`` ``QuerySet``: - - * A string, which may contain multiple tag names. - * A list or tuple of strings corresponding to tag names. - * A list or tuple of integers corresponding to tag ids. - - If given, the following will be returned as-is: - - * A list or tuple of ``Tag`` objects. - * A ``Tag`` ``QuerySet``. - - """ - from tagging.models import Tag - if isinstance(tags, Tag): - return [tags] - elif isinstance(tags, QuerySet) and tags.model is Tag: - return tags - elif isinstance(tags, types.StringTypes): - return Tag.objects.filter(name__in=parse_tag_input(tags)) - elif isinstance(tags, (types.ListType, types.TupleType)): - if len(tags) == 0: - return tags - contents = set() - for item in tags: - if isinstance(item, types.StringTypes): - contents.add('string') - elif isinstance(item, Tag): - contents.add('tag') - elif isinstance(item, (types.IntType, types.LongType)): - contents.add('int') - if len(contents) == 1: - if 'string' in contents: - return Tag.objects.filter(name__in=[force_unicode(tag) \ - for tag in tags]) - elif 'tag' in contents: - return tags - elif 'int' in contents: - return Tag.objects.filter(id__in=tags) - else: - raise ValueError(_('If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.')) - else: - raise ValueError(_('The tag input given was invalid.')) - -def get_tag(tag): - """ - Utility function for accepting single tag input in a flexible - manner. - - If a ``Tag`` object is given it will be returned as-is; if a - string or integer are given, they will be used to lookup the - appropriate ``Tag``. - - If no matching tag can be found, ``None`` will be returned. - """ - from tagging.models import Tag - if isinstance(tag, Tag): - return tag - - try: - if isinstance(tag, types.StringTypes): - return Tag.objects.get(name=tag) - elif isinstance(tag, (types.IntType, types.LongType)): - return Tag.objects.get(id=tag) - except Tag.DoesNotExist: - pass - - return None - -# Font size distribution algorithms -LOGARITHMIC, LINEAR = 1, 2 - -def _calculate_thresholds(min_weight, max_weight, steps): - delta = (max_weight - min_weight) / float(steps) - return [min_weight + i * delta for i in range(1, steps + 1)] - -def _calculate_tag_weight(weight, max_weight, distribution): - """ - Logarithmic tag weight calculation is based on code from the - `Tag Cloud`_ plugin for Mephisto, by Sven Fuchs. - - .. _`Tag Cloud`: http://www.artweb-design.de/projects/mephisto-plugin-tag-cloud - """ - if distribution == LINEAR or max_weight == 1: - return weight - elif distribution == LOGARITHMIC: - return math.log(weight) * max_weight / math.log(max_weight) - raise ValueError(_('Invalid distribution algorithm specified: %s.') % distribution) - -def calculate_cloud(tags, steps=4, distribution=LOGARITHMIC): - """ - Add a ``font_size`` attribute to each tag according to the - frequency of its use, as indicated by its ``count`` - attribute. - - ``steps`` defines the range of font sizes - ``font_size`` will - be an integer between 1 and ``steps`` (inclusive). - - ``distribution`` defines the type of font size distribution - algorithm which will be used - logarithmic or linear. It must be - one of ``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``. - """ - if len(tags) > 0: - counts = [tag.count for tag in tags] - min_weight = float(min(counts)) - max_weight = float(max(counts)) - thresholds = _calculate_thresholds(min_weight, max_weight, steps) - for tag in tags: - font_set = False - tag_weight = _calculate_tag_weight(tag.count, max_weight, distribution) - for i in range(steps): - if not font_set and tag_weight <= thresholds[i]: - tag.font_size = i + 1 - font_set = True - return tags diff --git a/tagging/.svn/text-base/validators.py.svn-base b/tagging/.svn/text-base/validators.py.svn-base deleted file mode 100644 index e902237..0000000 --- a/tagging/.svn/text-base/validators.py.svn-base +++ /dev/null @@ -1,30 +0,0 @@ -""" -Oldforms validators for tagging related fields - these are still -required for basic ``django.contrib.admin`` application field validation -until the ``newforms-admin`` branch lands in trunk. -""" -from django.core.validators import ValidationError -from django.utils.translation import ugettext as _ - -from tagging import settings -from tagging.utils import parse_tag_input - -def isTagList(field_data, all_data): - """ - Validates that ``field_data`` is a valid list of tags. - """ - for tag_name in parse_tag_input(field_data): - if len(tag_name) > settings.MAX_TAG_LENGTH: - raise ValidationError( - _('Each tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) - -def isTag(field_data, all_data): - """ - Validates that ``field_data`` is a valid tag. - """ - tag_names = parse_tag_input(field_data) - if len(tag_names) > 1: - raise ValidationError(_('Multiple tags were given.')) - elif len(tag_names[0]) > settings.MAX_TAG_LENGTH: - raise ValidationError( - _('A tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) diff --git a/tagging/.svn/text-base/views.py.svn-base b/tagging/.svn/text-base/views.py.svn-base deleted file mode 100644 index 9e7e2f5..0000000 --- a/tagging/.svn/text-base/views.py.svn-base +++ /dev/null @@ -1,52 +0,0 @@ -""" -Tagging related views. -""" -from django.http import Http404 -from django.utils.translation import ugettext as _ -from django.views.generic.list_detail import object_list - -from tagging.models import Tag, TaggedItem -from tagging.utils import get_tag, get_queryset_and_model - -def tagged_object_list(request, queryset_or_model=None, tag=None, - related_tags=False, related_tag_counts=True, **kwargs): - """ - A thin wrapper around - ``django.views.generic.list_detail.object_list`` which creates a - ``QuerySet`` containing instances of the given queryset or model - tagged with the given tag. - - In addition to the context variables set up by ``object_list``, a - ``tag`` context variable will contain the ``Tag`` instance for the - tag. - - If ``related_tags`` is ``True``, a ``related_tags`` context variable - will contain tags related to the given tag for the given model. - Additionally, if ``related_tag_counts`` is ``True``, each related - tag will have a ``count`` attribute indicating the number of items - which have it in addition to the given tag. - """ - if queryset_or_model is None: - try: - queryset_or_model = kwargs.pop('queryset_or_model') - except KeyError: - raise AttributeError(_('tagged_object_list must be called with a queryset or a model.')) - - if tag is None: - try: - tag = kwargs.pop('tag') - except KeyError: - raise AttributeError(_('tagged_object_list must be called with a tag.')) - - tag_instance = get_tag(tag) - if tag_instance is None: - raise Http404(_('No Tag found matching "%s".') % tag) - queryset = TaggedItem.objects.get_by_model(queryset_or_model, tag_instance) - if not kwargs.has_key('extra_context'): - kwargs['extra_context'] = {} - kwargs['extra_context']['tag'] = tag_instance - if related_tags: - kwargs['extra_context']['related_tags'] = \ - Tag.objects.related_for_model(tag_instance, queryset_or_model, - counts=related_tag_counts) - return object_list(request, queryset, **kwargs) diff --git a/tagging/admin.py b/tagging/admin.py new file mode 100644 index 0000000..4c1ef38 --- /dev/null +++ b/tagging/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from tagging.models import Tag, TaggedItem + +admin.site.register(TaggedItem) +admin.site.register(Tag) diff --git a/tagging/fields.py b/tagging/fields.py index 5b39e84..f52daff 100644 --- a/tagging/fields.py +++ b/tagging/fields.py @@ -8,7 +8,6 @@ from django.utils.translation import ugettext_lazy as _ from tagging import settings from tagging.models import Tag from tagging.utils import edit_string_for_tags -from tagging.validators import isTagList class TagField(CharField): """ @@ -19,7 +18,6 @@ class TagField(CharField): def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 255) kwargs['blank'] = kwargs.get('blank', True) - kwargs['validator_list'] = [isTagList] + kwargs.get('validator_list', []) super(TagField, self).__init__(*args, **kwargs) def contribute_to_class(self, cls, name): diff --git a/tagging/forms.py b/tagging/forms.py index e65aec4..a70ac6b 100644 --- a/tagging/forms.py +++ b/tagging/forms.py @@ -1,12 +1,28 @@ """ -Tagging components for Django's ``newforms`` form library. +Tagging components for Django's form library. """ from django import forms from django.utils.translation import ugettext as _ from tagging import settings +from tagging.models import Tag from tagging.utils import parse_tag_input +class AdminTagForm(forms.ModelForm): + class Meta: + model = Tag + + def clean_name(self): + value = self.cleaned_data['name'] + tag_names = parse_tag_input(value) + if len(tag_names) > 1: + raise ValidationError(_('Multiple tags were given.')) + elif len(tag_names[0]) > settings.MAX_TAG_LENGTH: + raise forms.ValidationError( + _('A tag may be no more than %s characters long.') % + settings.MAX_TAG_LENGTH) + return value + class TagField(forms.CharField): """ A ``CharField`` which validates that its input is a valid list of @@ -19,5 +35,6 @@ class TagField(forms.CharField): for tag_name in parse_tag_input(value): if len(tag_name) > settings.MAX_TAG_LENGTH: raise forms.ValidationError( - _('Each tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) + _('Each tag may be no more than %s characters long.') % + settings.MAX_TAG_LENGTH) return value diff --git a/tagging/managers.py b/tagging/managers.py index 51e2473..02cd1c2 100644 --- a/tagging/managers.py +++ b/tagging/managers.py @@ -23,7 +23,7 @@ class ModelTagManager(models.Manager): return Tag.objects.related_for_model(tags, self.model, *args, **kwargs) def usage(self, *args, **kwargs): - return Tag.objects.usage_for_model(self.model, *arg, **kwargs) + return Tag.objects.usage_for_model(self.model, *args, **kwargs) class ModelTaggedItemManager(models.Manager): """ diff --git a/tagging/models.py b/tagging/models.py index 7463ddb..d43f22d 100644 --- a/tagging/models.py +++ b/tagging/models.py @@ -16,15 +16,9 @@ from django.utils.translation import ugettext_lazy as _ from tagging import settings from tagging.utils import calculate_cloud, get_tag_list, get_queryset_and_model, parse_tag_input from tagging.utils import LOGARITHMIC -from tagging.validators import isTag qn = connection.ops.quote_name -try: - from django.db.models.query import parse_lookup -except ImportError: - parse_lookup = None - ############ # Managers # ############ @@ -147,23 +141,10 @@ class TagManager(models.Manager): """ if filters is None: filters = {} - if not parse_lookup: - # post-queryset-refactor (hand off to usage_for_queryset) - queryset = model._default_manager.filter() - for f in filters.items(): - queryset.query.add_filter(f) - usage = self.usage_for_queryset(queryset, counts, min_count) - else: - # pre-queryset-refactor - extra_joins = '' - extra_criteria = '' - params = [] - if len(filters) > 0: - joins, where, params = parse_lookup(filters.items(), model._meta) - extra_joins = ' '.join(['%s %s AS %s ON %s' % (join_type, table, alias, condition) - for (alias, (table, join_type, condition)) in joins.items()]) - extra_criteria = 'AND %s' % (' AND '.join(where)) - usage = self._get_usage(model, counts, min_count, extra_joins, extra_criteria, params) + queryset = model._default_manager.filter() + for f in filters.items(): + queryset.query.add_filter(f) + usage = self.usage_for_queryset(queryset, counts, min_count) return usage @@ -180,8 +161,6 @@ class TagManager(models.Manager): greater than or equal to ``min_count`` will be returned. Passing a value for ``min_count`` implies ``counts=True``. """ - if parse_lookup: - raise AttributeError("'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django.") extra_joins = ' '.join(queryset.query.get_from_clause()[0][1:]) where, params = queryset.query.where.as_sql() @@ -288,7 +267,7 @@ class TaggedItemManager(models.Manager): objects we're interested in, then use the ORM's ``__in`` lookup to return a ``QuerySet``. - Once the queryset-refactor branch lands in trunk, this can be + Now that the queryset-refactor branch is in the trunk, this can be tidied up significantly. """ def get_by_model(self, queryset_or_model, tags): @@ -440,11 +419,16 @@ class TaggedItemManager(models.Manager): 'tag': qn(self.model._meta.get_field('tag').rel.to._meta.db_table), 'content_type_id': content_type.pk, 'related_content_type_id': related_content_type.pk, - 'limit_offset': num is not None and connection.ops.limit_offset_sql(num) or '', + # Hardcoding this for now just to get tests working again - this + # should now be handled by the query object. + 'limit_offset': num is not None and 'LIMIT %s' or '', } cursor = connection.cursor() - cursor.execute(query, [obj.pk]) + params = [obj.pk] + if num is not None: + params.append(num) + cursor.execute(query, params) object_ids = [row[0] for row in cursor.fetchall()] if len(object_ids) > 0: # Use in_bulk here instead of an id__in lookup, because id__in would @@ -463,7 +447,7 @@ class Tag(models.Model): """ A tag. """ - name = models.CharField(_('name'), max_length=50, unique=True, db_index=True, validator_list=[isTag]) + name = models.CharField(_('name'), max_length=50, unique=True, db_index=True) objects = TagManager() @@ -472,9 +456,6 @@ class Tag(models.Model): verbose_name = _('tag') verbose_name_plural = _('tags') - class Admin: - pass - def __unicode__(self): return self.name @@ -495,8 +476,5 @@ class TaggedItem(models.Model): verbose_name = _('tagged item') verbose_name_plural = _('tagged items') - class Admin: - pass - def __unicode__(self): return u'%s [%s]' % (self.object, self.tag) diff --git a/tagging/templatetags/.svn/all-wcprops b/tagging/templatetags/.svn/all-wcprops deleted file mode 100644 index c3f4205..0000000 --- a/tagging/templatetags/.svn/all-wcprops +++ /dev/null @@ -1,17 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 44 -/svn/!svn/ver/114/trunk/tagging/templatetags -END -tagging_tags.py -K 25 -svn:wc:ra_dav:version-url -V 60 -/svn/!svn/ver/114/trunk/tagging/templatetags/tagging_tags.py -END -__init__.py -K 25 -svn:wc:ra_dav:version-url -V 55 -/svn/!svn/ver/93/trunk/tagging/templatetags/__init__.py -END diff --git a/tagging/templatetags/.svn/dir-prop-base b/tagging/templatetags/.svn/dir-prop-base deleted file mode 100644 index 4cc643b..0000000 --- a/tagging/templatetags/.svn/dir-prop-base +++ /dev/null @@ -1,6 +0,0 @@ -K 10 -svn:ignore -V 6 -*.pyc - -END diff --git a/tagging/templatetags/.svn/entries b/tagging/templatetags/.svn/entries deleted file mode 100644 index 3c6a555..0000000 --- a/tagging/templatetags/.svn/entries +++ /dev/null @@ -1,96 +0,0 @@ -9 - -dir -147 -http://django-tagging.googlecode.com/svn/trunk/tagging/templatetags -http://django-tagging.googlecode.com/svn - - - -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -83e7428b-ec2a-0410-86f2-bf466d0e5e72 - -tagging_tags.py -file - - - - -2008-08-21T18:05:40.000000Z -1deb2386b13645b6b26bc2a74e6bd6dc -2008-01-12T02:02:01.926198Z -114 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -8602 - -__init__.py -file - - - - -2008-08-21T18:05:40.000000Z -d41d8cd98f00b204e9800998ecf8427e -2007-08-20T09:19:55.193228Z -93 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -0 - diff --git a/tagging/templatetags/.svn/format b/tagging/templatetags/.svn/format deleted file mode 100644 index ec63514..0000000 --- a/tagging/templatetags/.svn/format +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/tagging/templatetags/.svn/prop-base/__init__.py.svn-base b/tagging/templatetags/.svn/prop-base/__init__.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/templatetags/.svn/prop-base/__init__.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/templatetags/.svn/prop-base/tagging_tags.py.svn-base b/tagging/templatetags/.svn/prop-base/tagging_tags.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/templatetags/.svn/prop-base/tagging_tags.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/templatetags/.svn/text-base/__init__.py.svn-base b/tagging/templatetags/.svn/text-base/__init__.py.svn-base deleted file mode 100644 index e69de29..0000000 --- a/tagging/templatetags/.svn/text-base/__init__.py.svn-base +++ /dev/null diff --git a/tagging/templatetags/.svn/text-base/tagging_tags.py.svn-base b/tagging/templatetags/.svn/text-base/tagging_tags.py.svn-base deleted file mode 100644 index 11d31cc..0000000 --- a/tagging/templatetags/.svn/text-base/tagging_tags.py.svn-base +++ /dev/null @@ -1,231 +0,0 @@ -from django.db.models import get_model -from django.template import Library, Node, TemplateSyntaxError, Variable, resolve_variable -from django.utils.translation import ugettext as _ - -from tagging.models import Tag, TaggedItem -from tagging.utils import LINEAR, LOGARITHMIC - -register = Library() - -class TagsForModelNode(Node): - def __init__(self, model, context_var, counts): - self.model = model - self.context_var = context_var - self.counts = counts - - def render(self, context): - model = get_model(*self.model.split('.')) - if model is None: - raise TemplateSyntaxError(_('tags_for_model tag was given an invalid model: %s') % self.model) - context[self.context_var] = Tag.objects.usage_for_model(model, counts=self.counts) - return '' - -class TagCloudForModelNode(Node): - def __init__(self, model, context_var, **kwargs): - self.model = model - self.context_var = context_var - self.kwargs = kwargs - - def render(self, context): - model = get_model(*self.model.split('.')) - if model is None: - raise TemplateSyntaxError(_('tag_cloud_for_model tag was given an invalid model: %s') % self.model) - context[self.context_var] = \ - Tag.objects.cloud_for_model(model, **self.kwargs) - return '' - -class TagsForObjectNode(Node): - def __init__(self, obj, context_var): - self.obj = Variable(obj) - self.context_var = context_var - - def render(self, context): - context[self.context_var] = \ - Tag.objects.get_for_object(self.obj.resolve(context)) - return '' - -class TaggedObjectsNode(Node): - def __init__(self, tag, model, context_var): - self.tag = Variable(tag) - self.context_var = context_var - self.model = model - - def render(self, context): - model = get_model(*self.model.split('.')) - if model is None: - raise TemplateSyntaxError(_('tagged_objects tag was given an invalid model: %s') % self.model) - context[self.context_var] = \ - TaggedItem.objects.get_by_model(model, self.tag.resolve(context)) - return '' - -def do_tags_for_model(parser, token): - """ - Retrieves a list of ``Tag`` objects associated with a given model - and stores them in a context variable. - - Usage:: - - {% tags_for_model [model] as [varname] %} - - The model is specified in ``[appname].[modelname]`` format. - - Extended usage:: - - {% tags_for_model [model] as [varname] with counts %} - - If specified - by providing extra ``with counts`` arguments - adds - a ``count`` attribute to each tag containing the number of - instances of the given model which have been tagged with it. - - Examples:: - - {% tags_for_model products.Widget as widget_tags %} - {% tags_for_model products.Widget as widget_tags with counts %} - - """ - bits = token.contents.split() - len_bits = len(bits) - if len_bits not in (4, 6): - raise TemplateSyntaxError(_('%s tag requires either three or five arguments') % bits[0]) - if bits[2] != 'as': - raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0]) - if len_bits == 6: - if bits[4] != 'with': - raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'with'") % bits[0]) - if bits[5] != 'counts': - raise TemplateSyntaxError(_("if given, fifth argument to %s tag must be 'counts'") % bits[0]) - if len_bits == 4: - return TagsForModelNode(bits[1], bits[3], counts=False) - else: - return TagsForModelNode(bits[1], bits[3], counts=True) - -def do_tag_cloud_for_model(parser, token): - """ - Retrieves a list of ``Tag`` objects for a given model, with tag - cloud attributes set, and stores them in a context variable. - - Usage:: - - {% tag_cloud_for_model [model] as [varname] %} - - The model is specified in ``[appname].[modelname]`` format. - - Extended usage:: - - {% tag_cloud_for_model [model] as [varname] with [options] %} - - Extra options can be provided after an optional ``with`` argument, - with each option being specified in ``[name]=[value]`` format. Valid - extra options are: - - ``steps`` - Integer. Defines the range of font sizes. - - ``min_count`` - Integer. Defines the minimum number of times a tag must have - been used to appear in the cloud. - - ``distribution`` - One of ``linear`` or ``log``. Defines the font-size - distribution algorithm to use when generating the tag cloud. - - Examples:: - - {% tag_cloud_for_model products.Widget as widget_tags %} - {% tag_cloud_for_model products.Widget as widget_tags with steps=9 min_count=3 distribution=log %} - - """ - bits = token.contents.split() - len_bits = len(bits) - if len_bits != 4 and len_bits not in range(6, 9): - raise TemplateSyntaxError(_('%s tag requires either three or between five and seven arguments') % bits[0]) - if bits[2] != 'as': - raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0]) - kwargs = {} - if len_bits > 5: - if bits[4] != 'with': - raise TemplateSyntaxError(_("if given, fourth argument to %s tag must be 'with'") % bits[0]) - for i in range(5, len_bits): - try: - name, value = bits[i].split('=') - if name == 'steps' or name == 'min_count': - try: - kwargs[str(name)] = int(value) - except ValueError: - raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid integer: '%(value)s'") % { - 'tag': bits[0], - 'option': name, - 'value': value, - }) - elif name == 'distribution': - if value in ['linear', 'log']: - kwargs[str(name)] = {'linear': LINEAR, 'log': LOGARITHMIC}[value] - else: - raise TemplateSyntaxError(_("%(tag)s tag's '%(option)s' option was not a valid choice: '%(value)s'") % { - 'tag': bits[0], - 'option': name, - 'value': value, - }) - else: - raise TemplateSyntaxError(_("%(tag)s tag was given an invalid option: '%(option)s'") % { - 'tag': bits[0], - 'option': name, - }) - except ValueError: - raise TemplateSyntaxError(_("%(tag)s tag was given a badly formatted option: '%(option)s'") % { - 'tag': bits[0], - 'option': bits[i], - }) - return TagCloudForModelNode(bits[1], bits[3], **kwargs) - -def do_tags_for_object(parser, token): - """ - Retrieves a list of ``Tag`` objects associated with an object and - stores them in a context variable. - - Usage:: - - {% tags_for_object [object] as [varname] %} - - Example:: - - {% tags_for_object foo_object as tag_list %} - """ - bits = token.contents.split() - if len(bits) != 4: - raise TemplateSyntaxError(_('%s tag requires exactly three arguments') % bits[0]) - if bits[2] != 'as': - raise TemplateSyntaxError(_("second argument to %s tag must be 'as'") % bits[0]) - return TagsForObjectNode(bits[1], bits[3]) - -def do_tagged_objects(parser, token): - """ - Retrieves a list of instances of a given model which are tagged with - a given ``Tag`` and stores them in a context variable. - - Usage:: - - {% tagged_objects [tag] in [model] as [varname] %} - - The model is specified in ``[appname].[modelname]`` format. - - The tag must be an instance of a ``Tag``, not the name of a tag. - - Example:: - - {% tagged_objects comedy_tag in tv.Show as comedies %} - - """ - bits = token.contents.split() - if len(bits) != 6: - raise TemplateSyntaxError(_('%s tag requires exactly five arguments') % bits[0]) - if bits[2] != 'in': - raise TemplateSyntaxError(_("second argument to %s tag must be 'in'") % bits[0]) - if bits[4] != 'as': - raise TemplateSyntaxError(_("fourth argument to %s tag must be 'as'") % bits[0]) - return TaggedObjectsNode(bits[1], bits[3], bits[5]) - -register.tag('tags_for_model', do_tags_for_model) -register.tag('tag_cloud_for_model', do_tag_cloud_for_model) -register.tag('tags_for_object', do_tags_for_object) -register.tag('tagged_objects', do_tagged_objects) diff --git a/tagging/tests/.svn/all-wcprops b/tagging/tests/.svn/all-wcprops deleted file mode 100644 index 1baeebb..0000000 --- a/tagging/tests/.svn/all-wcprops +++ /dev/null @@ -1,35 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 37 -/svn/!svn/ver/137/trunk/tagging/tests -END -__init__.py -K 25 -svn:wc:ra_dav:version-url -V 48 -/svn/!svn/ver/93/trunk/tagging/tests/__init__.py -END -settings.py -K 25 -svn:wc:ra_dav:version-url -V 49 -/svn/!svn/ver/137/trunk/tagging/tests/settings.py -END -tests.py -K 25 -svn:wc:ra_dav:version-url -V 46 -/svn/!svn/ver/136/trunk/tagging/tests/tests.py -END -models.py -K 25 -svn:wc:ra_dav:version-url -V 47 -/svn/!svn/ver/134/trunk/tagging/tests/models.py -END -tags.txt -K 25 -svn:wc:ra_dav:version-url -V 45 -/svn/!svn/ver/93/trunk/tagging/tests/tags.txt -END diff --git a/tagging/tests/.svn/dir-prop-base b/tagging/tests/.svn/dir-prop-base deleted file mode 100644 index 4cc643b..0000000 --- a/tagging/tests/.svn/dir-prop-base +++ /dev/null @@ -1,6 +0,0 @@ -K 10 -svn:ignore -V 6 -*.pyc - -END diff --git a/tagging/tests/.svn/entries b/tagging/tests/.svn/entries deleted file mode 100644 index c5dbe87..0000000 --- a/tagging/tests/.svn/entries +++ /dev/null @@ -1,198 +0,0 @@ -9 - -dir -147 -http://django-tagging.googlecode.com/svn/trunk/tagging/tests -http://django-tagging.googlecode.com/svn - - - -2008-05-04T20:49:27.458486Z -137 -jonathan.buchanan -has-props - -svn:special svn:externals svn:needs-lock - - - - - - - - - - - -83e7428b-ec2a-0410-86f2-bf466d0e5e72 - -__init__.py -file - - - - -2008-08-21T18:05:39.000000Z -d41d8cd98f00b204e9800998ecf8427e -2007-08-20T09:19:55.193228Z -93 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -0 - -settings.py -file - - - - -2008-08-21T18:05:39.000000Z -26ca4cbc5221da0059baefad5fffee9a -2008-05-04T20:49:27.458486Z -137 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -592 - -tests.py -file - - - - -2008-08-21T18:05:39.000000Z -20371aa8a5c90be3db9309a777756a52 -2008-05-04T20:48:43.707358Z -136 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -18675 - -models.py -file - - - - -2008-08-21T18:05:39.000000Z -25cb3f5db9701c5688534721a2d5e9ab -2008-04-30T22:40:28.286321Z -134 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -811 - -tags.txt -file - - - - -2008-08-21T18:05:39.000000Z -d4923e968e0692eac4758b364dba5c44 -2007-08-20T09:19:55.193228Z -93 -jonathan.buchanan -has-props - - - - - - - - - - - - - - - - - - - - -1382 - diff --git a/tagging/tests/.svn/format b/tagging/tests/.svn/format deleted file mode 100644 index ec63514..0000000 --- a/tagging/tests/.svn/format +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/tagging/tests/.svn/prop-base/__init__.py.svn-base b/tagging/tests/.svn/prop-base/__init__.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/tests/.svn/prop-base/__init__.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/tests/.svn/prop-base/models.py.svn-base b/tagging/tests/.svn/prop-base/models.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/tests/.svn/prop-base/models.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/tests/.svn/prop-base/settings.py.svn-base b/tagging/tests/.svn/prop-base/settings.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/tests/.svn/prop-base/settings.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/tests/.svn/prop-base/tags.txt.svn-base b/tagging/tests/.svn/prop-base/tags.txt.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/tests/.svn/prop-base/tags.txt.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/tests/.svn/prop-base/tests.py.svn-base b/tagging/tests/.svn/prop-base/tests.py.svn-base deleted file mode 100644 index bdbd305..0000000 --- a/tagging/tests/.svn/prop-base/tests.py.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:eol-style -V 6 -native -END diff --git a/tagging/tests/.svn/text-base/__init__.py.svn-base b/tagging/tests/.svn/text-base/__init__.py.svn-base deleted file mode 100644 index e69de29..0000000 --- a/tagging/tests/.svn/text-base/__init__.py.svn-base +++ /dev/null diff --git a/tagging/tests/.svn/text-base/models.py.svn-base b/tagging/tests/.svn/text-base/models.py.svn-base deleted file mode 100644 index 0708686..0000000 --- a/tagging/tests/.svn/text-base/models.py.svn-base +++ /dev/null @@ -1,38 +0,0 @@ -from django.db import models - -from tagging.fields import TagField - -class Perch(models.Model): - size = models.IntegerField() - smelly = models.BooleanField(default=True) - -class Parrot(models.Model): - state = models.CharField(max_length=50) - perch = models.ForeignKey(Perch, null=True) - - def __unicode__(self): - return self.state - - class Meta: - ordering = ['state'] - -class Link(models.Model): - name = models.CharField(max_length=50) - - def __unicode__(self): - return self.name - - class Meta: - ordering = ['name'] - -class Article(models.Model): - name = models.CharField(max_length=50) - - def __unicode__(self): - return self.name - - class Meta: - ordering = ['name'] - -class FormTest(models.Model): - tags = TagField('Test', help_text='Test') diff --git a/tagging/tests/.svn/text-base/settings.py.svn-base b/tagging/tests/.svn/text-base/settings.py.svn-base deleted file mode 100644 index 26e659b..0000000 --- a/tagging/tests/.svn/text-base/settings.py.svn-base +++ /dev/null @@ -1,27 +0,0 @@ -import os -DIRNAME = os.path.dirname(__file__) - -DEFAULT_CHARSET = 'utf-8' - -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(DIRNAME, 'tagging_test.db') - -#DATABASE_ENGINE = 'mysql' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'root' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '3306' - -#DATABASE_ENGINE = 'postgresql_psycopg2' -#DATABASE_NAME = 'tagging_test' -#DATABASE_USER = 'postgres' -#DATABASE_PASSWORD = '' -#DATABASE_HOST = 'localhost' -#DATABASE_PORT = '5432' - -INSTALLED_APPS = ( - 'django.contrib.contenttypes', - 'tagging', - 'tagging.tests', -) diff --git a/tagging/tests/.svn/text-base/tags.txt.svn-base b/tagging/tests/.svn/text-base/tags.txt.svn-base deleted file mode 100644 index 8543411..0000000 --- a/tagging/tests/.svn/text-base/tags.txt.svn-base +++ /dev/null @@ -1,122 +0,0 @@ -NewMedia 53 -Website 45 -PR 44 -Status 44 -Collaboration 41 -Drupal 34 -Journalism 31 -Transparency 30 -Theory 29 -Decentralization 25 -EchoChamberProject 24 -OpenSource 23 -Film 22 -Blog 21 -Interview 21 -Political 21 -Worldview 21 -Communications 19 -Conference 19 -Folksonomy 15 -MediaCriticism 15 -Volunteer 15 -Dialogue 13 -InternationalLaw 13 -Rosen 12 -Evolution 11 -KentBye 11 -Objectivity 11 -Plante 11 -ToDo 11 -Advisor 10 -Civics 10 -Roadmap 10 -Wilber 9 -About 8 -CivicSpace 8 -Ecosystem 8 -Choice 7 -Murphy 7 -Sociology 7 -ACH 6 -del.icio.us 6 -IntelligenceAnalysis 6 -Science 6 -Credibility 5 -Distribution 5 -Diversity 5 -Errors 5 -FinalCutPro 5 -Fundraising 5 -Law 5 -PhilosophyofScience 5 -Podcast 5 -PoliticalBias 5 -Activism 4 -Analysis 4 -CBS 4 -DeceptionDetection 4 -Editing 4 -History 4 -RSS 4 -Social 4 -Subjectivity 4 -Vlog 4 -ABC 3 -ALTubes 3 -Economics 3 -FCC 3 -NYT 3 -Sirota 3 -Sundance 3 -Training 3 -Wiki 3 -XML 3 -Borger 2 -Brody 2 -Deliberation 2 -EcoVillage 2 -Identity 2 -LAMP 2 -Lobe 2 -Maine 2 -May 2 -MediaLogic 2 -Metaphor 2 -Mitchell 2 -NBC 2 -OHanlon 2 -Psychology 2 -Queen 2 -Software 2 -SpiralDynamics 2 -Strobel 2 -Sustainability 2 -Transcripts 2 -Brown 1 -Buddhism 1 -Community 1 -DigitalDivide 1 -Donnelly 1 -Education 1 -FairUse 1 -FireANT 1 -Google 1 -HumanRights 1 -KM 1 -Kwiatkowski 1 -Landay 1 -Loiseau 1 -Math 1 -Music 1 -Nature 1 -Schechter 1 -Screencast 1 -Sivaraksa 1 -Skype 1 -SocialCapital 1 -TagCloud 1 -Thielmann 1 -Thomas 1 -Tiger 1 -Wedgwood 1
\ No newline at end of file diff --git a/tagging/tests/.svn/text-base/tests.py.svn-base b/tagging/tests/.svn/text-base/tests.py.svn-base deleted file mode 100644 index 574fa92..0000000 --- a/tagging/tests/.svn/text-base/tests.py.svn-base +++ /dev/null @@ -1,495 +0,0 @@ -# -*- coding: utf-8 -*- -tests = r""" ->>> import os ->>> from django import newforms as forms ->>> from tagging.forms import TagField ->>> from tagging import settings ->>> from tagging.models import Tag, TaggedItem ->>> from tagging.tests.models import Article, Link, Perch, Parrot, FormTest ->>> from tagging.utils import calculate_cloud, get_tag_list, get_tag, parse_tag_input ->>> from tagging.utils import LINEAR ->>> from tagging.validators import isTagList, isTag - -############# -# Utilities # -############# - -# Tag input ################################################################### - -# Simple space-delimited tags ->>> parse_tag_input('one') -[u'one'] ->>> parse_tag_input('one two') -[u'one', u'two'] ->>> parse_tag_input('one two three') -[u'one', u'three', u'two'] ->>> parse_tag_input('one one two two') -[u'one', u'two'] - -# Comma-delimited multiple words - an unquoted comma in the input will trigger -# this. ->>> parse_tag_input(',one') -[u'one'] ->>> parse_tag_input(',one two') -[u'one two'] ->>> parse_tag_input(',one two three') -[u'one two three'] ->>> parse_tag_input('a-one, a-two and a-three') -[u'a-one', u'a-two and a-three'] - -# Double-quoted multiple words - a completed quote will trigger this. -# Unclosed quotes are ignored. ->>> parse_tag_input('"one') -[u'one'] ->>> parse_tag_input('"one two') -[u'one', u'two'] ->>> parse_tag_input('"one two three') -[u'one', u'three', u'two'] ->>> parse_tag_input('"one two"') -[u'one two'] ->>> parse_tag_input('a-one "a-two and a-three"') -[u'a-one', u'a-two and a-three'] - -# No loose commas - split on spaces ->>> parse_tag_input('one two "thr,ee"') -[u'one', u'thr,ee', u'two'] - -# Loose commas - split on commas ->>> parse_tag_input('"one", two three') -[u'one', u'two three'] - -# Double quotes can contain commas ->>> parse_tag_input('a-one "a-two, and a-three"') -[u'a-one', u'a-two, and a-three'] ->>> parse_tag_input('"two", one, one, two, "one"') -[u'one', u'two'] - -# Bad users! Naughty users! ->>> parse_tag_input(None) -[] ->>> parse_tag_input('') -[] ->>> parse_tag_input('"') -[] ->>> parse_tag_input('""') -[] ->>> parse_tag_input('"' * 7) -[] ->>> parse_tag_input(',,,,,,') -[] ->>> parse_tag_input('",",",",",",","') -[u','] ->>> parse_tag_input('a-one "a-two" and "a-three') -[u'a-one', u'a-three', u'a-two', u'and'] - -# Normalised Tag list input ################################################### ->>> cheese = Tag.objects.create(name='cheese') ->>> toast = Tag.objects.create(name='toast') ->>> get_tag_list(cheese) -[<Tag: cheese>] ->>> get_tag_list('cheese toast') -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list('cheese,toast') -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list([]) -[] ->>> get_tag_list(['cheese', 'toast']) -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list([cheese.id, toast.id]) -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list(['cheese', 'toast', 'ŠĐĆŽćžšđ']) -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list([cheese, toast]) -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list((cheese, toast)) -(<Tag: cheese>, <Tag: toast>) ->>> get_tag_list(Tag.objects.filter(name__in=['cheese', 'toast'])) -[<Tag: cheese>, <Tag: toast>] ->>> get_tag_list(['cheese', toast]) -Traceback (most recent call last): - ... -ValueError: If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids. ->>> get_tag_list(29) -Traceback (most recent call last): - ... -ValueError: The tag input given was invalid. - -# Normalised Tag input ->>> get_tag(cheese) -<Tag: cheese> ->>> get_tag('cheese') -<Tag: cheese> ->>> get_tag(cheese.id) -<Tag: cheese> ->>> get_tag('mouse') - -# Tag clouds ################################################################## ->>> tags = [] ->>> for line in open(os.path.join(os.path.dirname(__file__), 'tags.txt')).readlines(): -... name, count = line.rstrip().split() -... tag = Tag(name=name) -... tag.count = int(count) -... tags.append(tag) - ->>> sizes = {} ->>> for tag in calculate_cloud(tags, steps=5): -... sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1 - -# This isn't a pre-calculated test, just making sure it's consistent ->>> sizes -{1: 48, 2: 30, 3: 19, 4: 15, 5: 10} - ->>> sizes = {} ->>> for tag in calculate_cloud(tags, steps=5, distribution=LINEAR): -... sizes[tag.font_size] = sizes.get(tag.font_size, 0) + 1 - -# This isn't a pre-calculated test, just making sure it's consistent ->>> sizes -{1: 97, 2: 12, 3: 7, 4: 2, 5: 4} - ->>> calculate_cloud(tags, steps=5, distribution='cheese') -Traceback (most recent call last): - ... -ValueError: Invalid distribution algorithm specified: cheese. - -# Validators ################################################################## - ->>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Each tag may be no more than 50 characters long.'] - ->>> isTag('"test"', {}) ->>> isTag(',test', {}) ->>> isTag('f o o', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Multiple tags were given.'] ->>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Each tag may be no more than 50 characters long.'] - -########### -# Tagging # -########### - -# Basic tagging ############################################################### - ->>> dead = Parrot.objects.create(state='dead') ->>> Tag.objects.update_tags(dead, 'foo,bar,"ter"') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: foo>, <Tag: ter>] ->>> Tag.objects.update_tags(dead, '"foo" bar "baz"') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>] ->>> Tag.objects.add_tag(dead, 'foo') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>] ->>> Tag.objects.add_tag(dead, 'zip') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>] ->>> Tag.objects.add_tag(dead, ' ') -Traceback (most recent call last): - ... -AttributeError: No tags were given: " ". ->>> Tag.objects.add_tag(dead, 'one two') -Traceback (most recent call last): - ... -AttributeError: Multiple tags were given: "one two". - -# Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii -# characters in output, so we're displaying the repr() here. ->>> Tag.objects.update_tags(dead, 'ŠĐĆŽćžšđ') ->>> repr(Tag.objects.get_for_object(dead)) -'[<Tag: \xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91>]' - ->>> Tag.objects.update_tags(dead, None) ->>> Tag.objects.get_for_object(dead) -[] - -# Using a model's TagField ->>> f1 = FormTest.objects.create(tags=u'test3 test2 test1') ->>> Tag.objects.get_for_object(f1) -[<Tag: test1>, <Tag: test2>, <Tag: test3>] ->>> f1.tags = u'test4' ->>> f1.save() ->>> Tag.objects.get_for_object(f1) -[<Tag: test4>] ->>> f1.tags = '' ->>> f1.save() ->>> Tag.objects.get_for_object(f1) -[] - -# Forcing tags to lowercase ->>> settings.FORCE_LOWERCASE_TAGS = True ->>> Tag.objects.update_tags(dead, 'foO bAr Ter') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: foo>, <Tag: ter>] ->>> Tag.objects.update_tags(dead, 'foO bAr baZ') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>] ->>> Tag.objects.add_tag(dead, 'FOO') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>] ->>> Tag.objects.add_tag(dead, 'Zip') ->>> Tag.objects.get_for_object(dead) -[<Tag: bar>, <Tag: baz>, <Tag: foo>, <Tag: zip>] ->>> Tag.objects.update_tags(dead, None) ->>> f1.tags = u'TEST5' ->>> f1.save() ->>> Tag.objects.get_for_object(f1) -[<Tag: test5>] ->>> f1.tags -u'test5' - -# Retrieving tags by Model #################################################### - ->>> Tag.objects.usage_for_model(Parrot) -[] ->>> parrot_details = ( -... ('pining for the fjords', 9, True, 'foo bar'), -... ('passed on', 6, False, 'bar baz ter'), -... ('no more', 4, True, 'foo ter'), -... ('late', 2, False, 'bar ter'), -... ) - ->>> for state, perch_size, perch_smelly, tags in parrot_details: -... perch = Perch.objects.create(size=perch_size, smelly=perch_smelly) -... parrot = Parrot.objects.create(state=state, perch=perch) -... Tag.objects.update_tags(parrot, tags) - ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True)] -[(u'bar', 3), (u'baz', 1), (u'foo', 2), (u'ter', 3)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2)] -[(u'bar', 3), (u'foo', 2), (u'ter', 3)] - -# Limiting results to a subset of the model ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state='no more'))] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(state__startswith='p'))] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__size__gt=4))] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, counts=True, filters=dict(perch__smelly=True))] -[(u'bar', 1), (u'foo', 2), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_model(Parrot, min_count=2, filters=dict(perch__smelly=True))] -[(u'foo', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=4))] -[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_model(Parrot, filters=dict(perch__size__gt=99))] -[] - -# Related tags ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=True)] -[(u'baz', 1), (u'foo', 1), (u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, min_count=2)] -[(u'ter', 2)] ->>> [tag.name for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar']), Parrot, counts=False)] -[u'baz', u'foo', u'ter'] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter']), Parrot, counts=True)] -[(u'baz', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(Tag.objects.filter(name__in=['bar', 'ter', 'baz']), Parrot, counts=True)] -[] - -# Once again, with feeling (strings) ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, counts=True)] -[(u'baz', 1), (u'foo', 1), (u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model('bar', Parrot, min_count=2)] -[(u'ter', 2)] ->>> [tag.name for tag in Tag.objects.related_for_model('bar', Parrot, counts=False)] -[u'baz', u'foo', u'ter'] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter'], Parrot, counts=True)] -[(u'baz', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.related_for_model(['bar', 'ter', 'baz'], Parrot, counts=True)] -[] - -# Retrieving tagged objects by Model ########################################## - ->>> foo = Tag.objects.get(name='foo') ->>> bar = Tag.objects.get(name='bar') ->>> baz = Tag.objects.get(name='baz') ->>> ter = Tag.objects.get(name='ter') ->>> TaggedItem.objects.get_by_model(Parrot, foo) -[<Parrot: no more>, <Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_by_model(Parrot, bar) -[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>] - -# Intersections are supported ->>> TaggedItem.objects.get_by_model(Parrot, [foo, baz]) -[] ->>> TaggedItem.objects.get_by_model(Parrot, [foo, bar]) -[<Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_by_model(Parrot, [bar, ter]) -[<Parrot: late>, <Parrot: passed on>] - -# Issue 114 - Intersection with non-existant tags ->>> TaggedItem.objects.get_intersection_by_model(Parrot, []) -[] - -# You can also pass Tag QuerySets ->>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'baz'])) -[] ->>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['foo', 'bar'])) -[<Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_by_model(Parrot, Tag.objects.filter(name__in=['bar', 'ter'])) -[<Parrot: late>, <Parrot: passed on>] - -# You can also pass strings and lists of strings ->>> TaggedItem.objects.get_by_model(Parrot, 'foo baz') -[] ->>> TaggedItem.objects.get_by_model(Parrot, 'foo bar') -[<Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_by_model(Parrot, 'bar ter') -[<Parrot: late>, <Parrot: passed on>] ->>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'baz']) -[] ->>> TaggedItem.objects.get_by_model(Parrot, ['foo', 'bar']) -[<Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_by_model(Parrot, ['bar', 'ter']) -[<Parrot: late>, <Parrot: passed on>] - -# Issue 50 - Get by non-existent tag ->>> TaggedItem.objects.get_by_model(Parrot, 'argatrons') -[] - -# Unions ->>> TaggedItem.objects.get_union_by_model(Parrot, ['foo', 'ter']) -[<Parrot: late>, <Parrot: no more>, <Parrot: passed on>, <Parrot: pining for the fjords>] ->>> TaggedItem.objects.get_union_by_model(Parrot, ['bar', 'baz']) -[<Parrot: late>, <Parrot: passed on>, <Parrot: pining for the fjords>] - -# Issue 114 - Union with non-existant tags ->>> TaggedItem.objects.get_union_by_model(Parrot, []) -[] - -# Retrieving related objects by Model ######################################### - -# Related instances of the same Model ->>> l1 = Link.objects.create(name='link 1') ->>> Tag.objects.update_tags(l1, 'tag1 tag2 tag3 tag4 tag5') ->>> l2 = Link.objects.create(name='link 2') ->>> Tag.objects.update_tags(l2, 'tag1 tag2 tag3') ->>> l3 = Link.objects.create(name='link 3') ->>> Tag.objects.update_tags(l3, 'tag1') ->>> l4 = Link.objects.create(name='link 4') ->>> TaggedItem.objects.get_related(l1, Link) -[<Link: link 2>, <Link: link 3>] ->>> TaggedItem.objects.get_related(l1, Link, num=1) -[<Link: link 2>] ->>> TaggedItem.objects.get_related(l4, Link) -[] - -# Limit related items ->>> TaggedItem.objects.get_related(l1, Link.objects.exclude(name='link 3')) -[<Link: link 2>] - -# Related instance of a different Model ->>> a1 = Article.objects.create(name='article 1') ->>> Tag.objects.update_tags(a1, 'tag1 tag2 tag3 tag4') ->>> TaggedItem.objects.get_related(a1, Link) -[<Link: link 1>, <Link: link 2>, <Link: link 3>] ->>> Tag.objects.update_tags(a1, 'tag6') ->>> TaggedItem.objects.get_related(a1, Link) -[] - -################ -# Model Fields # -################ - -# TagField #################################################################### - -# Ensure that automatically created forms use TagField ->>> class TestForm(forms.ModelForm): -... class Meta: -... model = FormTest ->>> form = TestForm() ->>> form.fields['tags'].__class__.__name__ -'TagField' - -# Recreating string representaions of tag lists ############################### ->>> plain = Tag.objects.create(name='plain') ->>> spaces = Tag.objects.create(name='spa ces') ->>> comma = Tag.objects.create(name='com,ma') - ->>> from tagging.utils import edit_string_for_tags ->>> edit_string_for_tags([plain]) -u'plain' ->>> edit_string_for_tags([plain, spaces]) -u'plain, spa ces' ->>> edit_string_for_tags([plain, spaces, comma]) -u'plain, spa ces, "com,ma"' ->>> edit_string_for_tags([plain, comma]) -u'plain "com,ma"' ->>> edit_string_for_tags([comma, spaces]) -u'"com,ma", spa ces' - -############### -# Form Fields # -############### - ->>> t = TagField() ->>> t.clean('foo') -u'foo' ->>> t.clean('foo bar baz') -u'foo bar baz' ->>> t.clean('foo,bar,baz') -u'foo,bar,baz' ->>> t.clean('foo, bar, baz') -u'foo, bar, baz' ->>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar') -u'foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvb bar' ->>> t.clean('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar') -Traceback (most recent call last): - ... -ValidationError: [u'Each tag may be no more than 50 characters long.'] -""" - -tests_pre_qsrf = tests + r""" -# Limiting results to a queryset ->>> Tag.objects.usage_for_queryset(Parrot.objects.filter()) -Traceback (most recent call last): - ... -AttributeError: 'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django. -""" - -tests_post_qsrf = tests + r""" ->>> from django.db.models import Q - -# Limiting results to a queryset ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state='no more'), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state__startswith='p'), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), counts=True)] -[(u'bar', 1), (u'foo', 2), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), min_count=2)] -[(u'foo', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4))] -[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=99))] -[] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), counts=True)] -[(u'bar', 2), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), min_count=2)] -[(u'bar', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')))] -[(u'bar', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state='passed on'), counts=True)] -[(u'bar', 2), (u'foo', 2), (u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state__startswith='p'), min_count=2)] -[(u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(Q(perch__size__gt=6) | Q(perch__smelly=False)), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(perch__smelly=True).filter(state__startswith='l'), counts=True)] -[(u'bar', 1), (u'ter', 1)] -""" - -try: - from django.db.models.query import parse_lookup -except ImportError: - __test__ = {'post-qsrf': tests_post_qsrf} -else: - __test__ = {'pre-qsrf': tests_pre_qsrf} diff --git a/tagging/tests/tests.py b/tagging/tests/tests.py index 574fa92..5d6d8b5 100644 --- a/tagging/tests/tests.py +++ b/tagging/tests/tests.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- -tests = r""" +r""" >>> import os ->>> from django import newforms as forms +>>> from django import forms +>>> from django.db.models import Q >>> from tagging.forms import TagField >>> from tagging import settings >>> from tagging.models import Tag, TaggedItem >>> from tagging.tests.models import Article, Link, Perch, Parrot, FormTest >>> from tagging.utils import calculate_cloud, get_tag_list, get_tag, parse_tag_input >>> from tagging.utils import LINEAR ->>> from tagging.validators import isTagList, isTag ############# # Utilities # @@ -152,24 +152,6 @@ Traceback (most recent call last): ... ValueError: Invalid distribution algorithm specified: cheese. -# Validators ################################################################## - ->>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Each tag may be no more than 50 characters long.'] - ->>> isTag('"test"', {}) ->>> isTag(',test', {}) ->>> isTag('f o o', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Multiple tags were given.'] ->>> isTagList('foo qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbn bar', {}) -Traceback (most recent call last): - ... -ValidationError: [u'Each tag may be no more than 50 characters long.'] - ########### # Tagging # ########### @@ -393,6 +375,36 @@ u'test5' >>> TaggedItem.objects.get_related(a1, Link) [] +# Limiting results to a queryset +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state='no more'), counts=True)] +[(u'foo', 1), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state__startswith='p'), counts=True)] +[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4), counts=True)] +[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), counts=True)] +[(u'bar', 1), (u'foo', 2), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), min_count=2)] +[(u'foo', 2)] +>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4))] +[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)] +>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=99))] +[] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), counts=True)] +[(u'bar', 2), (u'foo', 1), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), min_count=2)] +[(u'bar', 2)] +>>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')))] +[(u'bar', False), (u'foo', False), (u'ter', False)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state='passed on'), counts=True)] +[(u'bar', 2), (u'foo', 2), (u'ter', 2)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state__startswith='p'), min_count=2)] +[(u'ter', 2)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(Q(perch__size__gt=6) | Q(perch__smelly=False)), counts=True)] +[(u'foo', 1), (u'ter', 1)] +>>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(perch__smelly=True).filter(state__startswith='l'), counts=True)] +[(u'bar', 1), (u'ter', 1)] + ################ # Model Fields # ################ @@ -444,52 +456,3 @@ Traceback (most recent call last): ... ValidationError: [u'Each tag may be no more than 50 characters long.'] """ - -tests_pre_qsrf = tests + r""" -# Limiting results to a queryset ->>> Tag.objects.usage_for_queryset(Parrot.objects.filter()) -Traceback (most recent call last): - ... -AttributeError: 'TagManager.usage_for_queryset' is not compatible with pre-queryset-refactor versions of Django. -""" - -tests_post_qsrf = tests + r""" ->>> from django.db.models import Q - -# Limiting results to a queryset ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state='no more'), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(state__startswith='p'), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4), counts=True)] -[(u'bar', 2), (u'baz', 1), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), counts=True)] -[(u'bar', 1), (u'foo', 2), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__smelly=True), min_count=2)] -[(u'foo', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=4))] -[(u'bar', False), (u'baz', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(perch__size__gt=99))] -[] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), counts=True)] -[(u'bar', 2), (u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')), min_count=2)] -[(u'bar', 2)] ->>> [(tag.name, hasattr(tag, 'counts')) for tag in Tag.objects.usage_for_queryset(Parrot.objects.filter(Q(perch__size__gt=6) | Q(state__startswith='l')))] -[(u'bar', False), (u'foo', False), (u'ter', False)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state='passed on'), counts=True)] -[(u'bar', 2), (u'foo', 2), (u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(state__startswith='p'), min_count=2)] -[(u'ter', 2)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(Q(perch__size__gt=6) | Q(perch__smelly=False)), counts=True)] -[(u'foo', 1), (u'ter', 1)] ->>> [(tag.name, tag.count) for tag in Tag.objects.usage_for_queryset(Parrot.objects.exclude(perch__smelly=True).filter(state__startswith='l'), counts=True)] -[(u'bar', 1), (u'ter', 1)] -""" - -try: - from django.db.models.query import parse_lookup -except ImportError: - __test__ = {'post-qsrf': tests_post_qsrf} -else: - __test__ = {'pre-qsrf': tests_pre_qsrf} diff --git a/tagging/validators.py b/tagging/validators.py deleted file mode 100644 index e902237..0000000 --- a/tagging/validators.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Oldforms validators for tagging related fields - these are still -required for basic ``django.contrib.admin`` application field validation -until the ``newforms-admin`` branch lands in trunk. -""" -from django.core.validators import ValidationError -from django.utils.translation import ugettext as _ - -from tagging import settings -from tagging.utils import parse_tag_input - -def isTagList(field_data, all_data): - """ - Validates that ``field_data`` is a valid list of tags. - """ - for tag_name in parse_tag_input(field_data): - if len(tag_name) > settings.MAX_TAG_LENGTH: - raise ValidationError( - _('Each tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) - -def isTag(field_data, all_data): - """ - Validates that ``field_data`` is a valid tag. - """ - tag_names = parse_tag_input(field_data) - if len(tag_names) > 1: - raise ValidationError(_('Multiple tags were given.')) - elif len(tag_names[0]) > settings.MAX_TAG_LENGTH: - raise ValidationError( - _('A tag may be no more than %s characters long.') % settings.MAX_TAG_LENGTH) |