Skip to content

Commit

Permalink
Implemented generic_m2m support
Browse files Browse the repository at this point in the history
  • Loading branch information
jpic committed May 21, 2012
1 parent 5ee7076 commit 8f2ce81
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 18 deletions.
2 changes: 1 addition & 1 deletion autocomplete_light/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Empty file.
83 changes: 83 additions & 0 deletions autocomplete_light/contrib/generic_m2m.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions autocomplete_light/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
11 changes: 7 additions & 4 deletions cloc.txt
Original file line number Diff line number Diff line change
@@ -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
-------------------------------------------------------------------------------

Expand Down
49 changes: 36 additions & 13 deletions docs/source/generic.rst
Original file line number Diff line number Diff line change
@@ -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
Binary file modified test_project/db.sqlite
Binary file not shown.
Empty file.
9 changes: 9 additions & 0 deletions test_project/generic_m2m_example/admin.py
Original file line number Diff line number Diff line change
@@ -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)
13 changes: 13 additions & 0 deletions test_project/generic_m2m_example/forms.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions test_project/generic_m2m_example/models.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions test_project/project_specific/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions test_project/test_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
'south',
'autocomplete_light',
'project_specific',
'genericm2m',
'generic_m2m_example',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
Expand Down

0 comments on commit 8f2ce81

Please sign in to comment.