Skip to content
Browse files

Implemented generic_m2m support

  • Loading branch information...
1 parent 5ee7076 commit 8f2ce81a74c1e9d558e59fb56514ef166c3f8e39 @jpic jpic committed May 21, 2012
View
2 autocomplete_light/__init__.py
@@ -6,4 +6,4 @@
GenericChannelBase
from .widgets import AutocompleteWidget
from .forms import get_widgets_dict, modelform_factory
-from .generic import GenericForeignKeyField, GenericModelForm
+from .generic import GenericModelForm, GenericForeignKeyField
View
0 autocomplete_light/contrib/__init__.py
No changes.
View
83 autocomplete_light/contrib/generic_m2m.py
@@ -0,0 +1,83 @@
+"""
+``autocomplete_light.contrib.generic_m2m`` couples django-autocomplete-light
+with django-generic-m2m.
+
+Generic many to many are supported since 0.5. It depends on `django-generic-m2m
+<http://django-generic-m2m.rtfd.org>`_ external apps. Follow django-generic-m2m
+installation documentation, but at the time of writing it barely consists of
+adding the ``genericm2m`` to ``INSTALLED_APPS``, and adding a field to models
+that should have a generic m2m relation. So, kudos to the maintainers of
+django-generic-m2m, fantastic app, use it for generic many to many relations.
+
+See examples in ``test_project/generic_m2m_example``.
+"""
+from genericm2m.models import RelatedObjectsDescriptor
+
+from ..generic import GenericForeignKeyField, GenericModelForm
+
+
+class GenericModelForm(GenericModelForm):
+ """
+ Extension of autocomplete_light.GenericModelForm, that handles
+ genericm2m's RelatedObjectsDescriptor.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """
+ Add related objects to initial for each generic m2m field.
+ """
+ super(GenericModelForm, self).__init__(*args, **kwargs)
+
+ for name, field in self.generic_m2m_fields():
+ related_objects = getattr(self.instance, name).all()
+ self.initial[name] = [x.object for x in related_objects]
+
+ def generic_m2m_fields(self):
+ """
+ Yield name, field for each RelatedObjectsDescriptor of the model of
+ this ModelForm.
+ """
+ for name, field in self.fields.items():
+ if not isinstance(field, GenericManyToMany):
+ continue
+
+ model_class_attr = getattr(self._meta.model, name, None)
+ if not isinstance(model_class_attr, RelatedObjectsDescriptor):
+ continue
+
+ yield name, field
+
+ def save(self, commit=True):
+ """
+ Save selected generic m2m relations after saving the model.
+ """
+ model = super(GenericModelForm, self).save(commit)
+
+ for name, field in self.generic_m2m_fields():
+ model_attr = getattr(model, name)
+ selected_relations = self.cleaned_data.get(name, [])
+
+ for related in model_attr.all():
+ if related.object not in selected_relations:
+ model_attr.remove(related)
+
+ for related in selected_relations:
+ model_attr.connect(related)
+
+ return model
+
+
+class GenericManyToMany(GenericForeignKeyField):
+ """
+ Simple form field that converts strings to models.
+ """
+ def prepare_value(self, value):
+ if hasattr(value, '__iter__'):
+ return [super(GenericManyToMany, self).prepare_value(v) \
+ for v in value]
+ return super(GenericManyToMany, self).prepare_value(value)
+
+ def to_python(self, value):
+ if hasattr(value, '__iter__'):
+ return [super(GenericManyToMany, self).to_python(v) for v in value]
+ return super(GenericManyToMany, self).to_python(value)
View
2 autocomplete_light/generic.py
@@ -4,6 +4,8 @@
from django.contrib.contenttypes.generic import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
+__all__ = ('GenericModelForm', 'GenericForeignKeyField')
+
class GenericModelForm(forms.ModelForm):
"""
View
11 cloc.txt
@@ -1,12 +1,15 @@
-http://cloc.sourceforge.net v 1.53 T=0.5 s (40.0 files/s, 3562.0 lines/s)
+ 23 text files.
+classified 23 files 23 unique files.
+ 3 files ignored.
+
+http://cloc.sourceforge.net v 1.53 T=0.5 s (42.0 files/s, 3732.0 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
-Python 13 261 485 477
+Python 14 280 513 515
Javascript 3 54 47 385
HTML 3 9 0 50
CSS 1 0 0 13
-------------------------------------------------------------------------------
-SUM: 20 324 532 925
+SUM: 21 343 560 963
-------------------------------------------------------------------------------
-
View
49 docs/source/generic.rst
@@ -1,29 +1,52 @@
-Generic relations support
+GenericForeignKey support
=========================
Generic foreign keys are supported since 0.4.
-Example
--------
-
-Such a form:
+GenericChannelBase
+------------------
-.. literalinclude:: ../../test_api_project/project_specific/generic_form_example.py
- :language: python
+.. automodule:: autocomplete_light.channel.generic
+ :members:
-Would work with such a channel:
+Example
+~~~~~~~
.. literalinclude:: ../../test_api_project/project_specific/generic_channel_example.py
:language: python
-Form
-----
+GenericForeignKeyField
+----------------------
.. automodule:: autocomplete_light.generic
:members:
-Channel
--------
+Example
+~~~~~~~
-.. automodule:: autocomplete_light.channel.generic
+.. literalinclude:: ../../test_api_project/project_specific/generic_form_example.py
+ :language: python
+
+GenericManyToMany
+-----------------
+
+.. automodule:: autocomplete_light.contrib.generic_m2m
:members:
+
+Example
+~~~~~~~
+
+Example model with ``related``:
+
+.. literalinclude:: ../../test_project/generic_m2m_example/models.py
+ :language: python
+
+Example ``generic_m2m.GenericModelForm`` usage:
+
+.. literalinclude:: ../../test_project/generic_m2m_example/forms.py
+ :language: python
+
+Example ``ModelAdmin``:
+
+.. literalinclude:: ../../test_project/generic_m2m_example/admin.py
+ :language: python
View
BIN test_project/db.sqlite
Binary file not shown.
View
0 test_project/generic_m2m_example/__init__.py
No changes.
View
9 test_project/generic_m2m_example/admin.py
@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+from models import ModelGroup
+from forms import ModelGroupForm
+
+
+class ModelGroupAdmin(admin.ModelAdmin):
+ form = ModelGroupForm
+admin.site.register(ModelGroup, ModelGroupAdmin)
View
13 test_project/generic_m2m_example/forms.py
@@ -0,0 +1,13 @@
+import autocomplete_light
+from autocomplete_light.contrib.generic_m2m import GenericModelForm, \
+ GenericManyToMany
+
+from models import ModelGroup
+
+
+class ModelGroupForm(GenericModelForm):
+ related = GenericManyToMany(
+ widget=autocomplete_light.AutocompleteWidget('MyGenericChannel'))
+
+ class Meta:
+ model = ModelGroup
View
13 test_project/generic_m2m_example/models.py
@@ -0,0 +1,13 @@
+from django.db import models
+from django.db.models import signals
+from django.contrib.contenttypes import generic
+
+from genericm2m.models import RelatedObjectsDescriptor
+
+class ModelGroup(models.Model):
+ name = models.CharField(max_length=100)
+
+ related = RelatedObjectsDescriptor()
+
+ def __unicode__(self):
+ return self.name
View
4 test_project/project_specific/models.py
@@ -3,6 +3,8 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
+from genericm2m.models import RelatedObjectsDescriptor
+
import cities_light
@@ -38,5 +40,7 @@ class TaggedItem(models.Model):
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
+ related = RelatedObjectsDescriptor()
+
def __unicode__(self):
return self.tag
View
2 test_project/test_project/settings.py
@@ -131,6 +131,8 @@
'south',
'autocomplete_light',
'project_specific',
+ 'genericm2m',
+ 'generic_m2m_example',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)

0 comments on commit 8f2ce81

Please sign in to comment.
Something went wrong with that request. Please try again.