Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Latest #2

Open
wants to merge 1 commit into
base: django-110-compat
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 11 additions & 58 deletions tagging/__init__.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,15 @@
VERSION = (0, 4, 0, "dev", 1)
"""
Django-tagging
"""
__version__ = '0.4.6'
__license__ = 'BSD License'

__author__ = 'Jonathan Buchanan'
__author_email__ = '[email protected]'

__maintainer__ = 'Fantomas42'
__maintainer_email__ = '[email protected]'

def get_version():
if VERSION[3] == "final":
return "%s.%s.%s" % (VERSION[0], VERSION[1], VERSION[2])
elif VERSION[3] == "dev":
if VERSION[2] == 0:
return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[3], VERSION[4])
return "%s.%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3], VERSION[4])
else:
return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3])
__url__ = 'https://github.com/Fantomas42/django-tagging'


__version__ = get_version()


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.
"""

from tagging.managers import ModelTaggedItemManager, TagDescriptor

if model in registry:
raise AlreadyRegistered("The model '%s' has already been "
"registered." % model._meta.object_name)
if hasattr(model, tag_descriptor_attr):
raise AttributeError("'%s' already has an attribute '%s'. You must "
"provide a custom tag_descriptor_attr to register." % (
model._meta.object_name,
tag_descriptor_attr,
)
)
if hasattr(model, tagged_item_manager_attr):
raise AttributeError("'%s' already has an attribute '%s'. You must "
"provide a custom tagged_item_manager_attr to register." % (
model._meta.object_name,
tagged_item_manager_attr,
)
)

# Add tag descriptor
setattr(model, tag_descriptor_attr, TagDescriptor())

# Add custom manager
ModelTaggedItemManager().contribute_to_class(model, tagged_item_manager_attr)

# Finally register in registry
registry.append(model)
default_app_config = 'tagging.apps.TaggingConfig'
13 changes: 8 additions & 5 deletions tagging/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""
Admin components for tagging.
"""
from django.contrib import admin
from tagging.models import Tag, TaggedItem

from tagging.models import Tag
from tagging.models import TaggedItem
from tagging.forms import TagAdminForm


class TagAdmin(admin.ModelAdmin):
form = TagAdminForm


admin.site.register(TaggedItem)
admin.site.register(Tag, TagAdmin)




14 changes: 14 additions & 0 deletions tagging/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Apps for tagging.
"""
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _


class TaggingConfig(AppConfig):
"""
Config for Tagging application.
"""
name = 'tagging'
label = 'tagging'
verbose_name = _('Tagging')
48 changes: 22 additions & 26 deletions tagging/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from tagging import settings
from tagging.models import Tag
from tagging.utils import edit_string_for_tags
from tagging.forms import TagField as TagFormField


class TagField(CharField):
"""
Expand All @@ -18,8 +20,6 @@ class TagField(CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 255)
kwargs['blank'] = kwargs.get('blank', True)
kwargs['default'] = kwargs.get('default', '')
self._initialized = False
super(TagField, self).__init__(*args, **kwargs)

def contribute_to_class(self, cls, name):
Expand Down Expand Up @@ -54,31 +54,34 @@ class Link(models.Model):
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)
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):
def _save(self, **kwargs): # signal, sender, instance):
"""
Save tags back to the database
"""
tags = self._get_instance_tag_cache(kwargs['instance'])
Tag.objects.update_tags(kwargs['instance'], tags)

def _update(self, **kwargs): #signal, sender, instance):
"""
Update tag cache from TaggedItem objects.
"""
instance = kwargs['instance']
self._update_instance_tag_cache(instance)
if tags is not None:
Tag.objects.update_tags(kwargs['instance'], tags)

def __delete__(self, instance):
"""
Expand All @@ -90,31 +93,24 @@ def _get_instance_tag_cache(self, instance):
"""
Helper: get an instance's tag cache.
"""
if not self._initialized:
self._initialized = True
self._update(instance=instance)
return getattr(instance, '_%s_cache' % self.attname, None)

def _set_instance_tag_cache(self, instance, tags):
"""
Helper: set an instance's tag cache.
"""
# The next instruction does nothing particular,
# but needed to by-pass the deferred fields system
# when saving an instance, which check the keys present
# in instance.__dict__.
# The issue is introducted in Django 1.10
instance.__dict__[self.attname] = tags
setattr(instance, '_%s_cache' % self.attname, tags)

def _update_instance_tag_cache(self, instance):
"""
Helper: update an instance's tag cache from actual Tags.
"""
# for an unsaved object, leave the default value alone
if instance.pk is not None:
tags = edit_string_for_tags(Tag.objects.get_for_object(instance))
self._set_instance_tag_cache(instance, tags)

def get_internal_type(self):
return 'CharField'

def formfield(self, **kwargs):
from tagging import forms
defaults = {'form_class': forms.TagField}
defaults = {'form_class': TagFormField}
defaults.update(kwargs)
return super(TagField, self).formfield(**defaults)
13 changes: 5 additions & 8 deletions tagging/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Tagging components for Django's form library.
Form components for tagging.
"""
from django import forms
from django.utils.translation import ugettext as _
Expand All @@ -8,33 +8,30 @@
from tagging.models import Tag
from tagging.utils import parse_tag_input


class TagAdminForm(forms.ModelForm):
class Meta:
model = Tag
fields = ('name',)

def clean_name(self):
value = self.cleaned_data['name']
tag_names = parse_tag_input(value)
if len(tag_names) > 1:
raise forms.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
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)
settings.MAX_TAG_LENGTH)
return value
13 changes: 10 additions & 3 deletions tagging/generic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""
Generic components for tagging.
"""
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
Expand All @@ -15,7 +19,8 @@ def fetch_content_objects(tagged_items, select_related_for=None):
``ContentType``) for which ``select_related`` should be used when
retrieving model instances.
"""
if select_related_for is None: select_related_for = []
if select_related_for is None:
select_related_for = []

# Group content object pks by their content type pks
objects = {}
Expand All @@ -27,9 +32,11 @@ def fetch_content_objects(tagged_items, select_related_for=None):
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)
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)
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'
Expand Down
13 changes: 8 additions & 5 deletions tagging/managers.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
"""
Custom managers for Django models registered with the tagging
application.
Custom managers for tagging.
"""
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.contrib.contenttypes.models import ContentType

from tagging.models import Tag
from tagging.models import TaggedItem

from tagging.models import Tag, TaggedItem

class ModelTagManager(models.Manager):
"""
A manager for retrieving tags for a particular model.
"""
def get_query_set(self):
def get_queryset(self):
ctype = ContentType.objects.get_for_model(self.model)
return Tag.objects.filter(
items__content_type__pk=ctype.pk).distinct()
Expand All @@ -25,6 +26,7 @@ def related(self, tags, *args, **kwargs):
def usage(self, *args, **kwargs):
return Tag.objects.usage_for_model(self.model, *args, **kwargs)


class ModelTaggedItemManager(models.Manager):
"""
A manager for retrieving model instances based on their tags.
Expand All @@ -47,6 +49,7 @@ def with_any(self, tags, queryset=None):
else:
return TaggedItem.objects.get_union_by_model(queryset, tags)


class TagDescriptor(object):
"""
A descriptor which provides access to a ``ModelTagManager`` for
Expand Down
54 changes: 54 additions & 0 deletions tagging/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.db import models
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('name', models.CharField(
unique=True, max_length=50,
verbose_name='name', db_index=True)),
],
options={
'ordering': ('name',),
'verbose_name': 'tag',
'verbose_name_plural': 'tags',
},
),
migrations.CreateModel(
name='TaggedItem',
fields=[
('id', models.AutoField(
verbose_name='ID', serialize=False,
auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField(
verbose_name='object id', db_index=True)),
('content_type', models.ForeignKey(
verbose_name='content type',
on_delete=models.SET_NULL,
to='contenttypes.ContentType')),
('tag', models.ForeignKey(
related_name='items', verbose_name='tag',
on_delete=models.SET_NULL,
to='tagging.Tag')),
],
options={
'verbose_name': 'tagged item',
'verbose_name_plural': 'tagged items',
},
),
migrations.AlterUniqueTogether(
name='taggeditem',
unique_together=set([('tag', 'content_type', 'object_id')]),
),
]
Loading