Skip to content

Commit

Permalink
Added possibility of excluding fields from moderation change list. Cl…
Browse files Browse the repository at this point in the history
…oses #23
  • Loading branch information
dominno committed Oct 2, 2010
1 parent aec5e13 commit b3c3122
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 34 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ GenericModerator options
``visibility_column``
If you want a performance boost, define visibility field on your model and add option ``visibility_column = 'your_field'`` on moderator class. Field must by a BooleanField. The manager that decides which model objects should be excluded when it were rejected, will first use this option to properly display (or hide) objects that are registered with moderation. Use this option if you can define visibility column in your model and want to boost performance. By default when accessing model objects that are under moderation, one extra query is executed per object in query set to determine if object should be excluded from query set. This method benefit those who do not want to add any fields to their Models. Default: None.

``fields_exclude``
Fields to exclude from object change list. Default: []

``auto_approve_for_superusers``
Auto approve objects changed by superusers. Default: True

Expand Down
2 changes: 2 additions & 0 deletions src/moderation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class GenericModerator(object):
moderation_manager_class = ModerationObjectsManager
bypass_moderation_after_approval = False

fields_exclude = []

visibility_column = None

auto_approve_for_superusers = True
Expand Down
8 changes: 6 additions & 2 deletions src/moderation/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from moderation.models import ModeratedObject, MODERATION_DRAFT_STATE,\
MODERATION_STATUS_PENDING, MODERATION_STATUS_REJECTED,\
MODERATION_STATUS_APPROVED
from moderation import moderation

from django.utils.translation import ugettext as _
from moderation.forms import BaseModeratedObjectForm
from moderation.helpers import automoderate
Expand Down Expand Up @@ -122,13 +122,17 @@ class Meta:
return ModeratedObjectForm

def change_view(self, request, object_id, extra_context=None):
from moderation import moderation
moderated_object = ModeratedObject.objects.get(pk=object_id)

changed_object = moderated_object.changed_object

moderator = moderation.get_moderator(changed_object.__class__)

changes = get_changes_between_models(
moderated_object.get_object_for_this_type(),
changed_object)
changed_object,
moderator.fields_exclude).values()
if request.POST:
admin_form = self.get_form(request, moderated_object)(request.POST)

Expand Down
42 changes: 30 additions & 12 deletions src/moderation/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,45 @@ def diff(self):
'right_image': right_image})



def get_change(model1, model2, field):
try:
value1 = getattr(model1, "get_%s_display" % field.name)()
value2 = getattr(model2, "get_%s_display" % field.name)()
except AttributeError:
value1 = field.value_from_object(model1)
value2 = field.value_from_object(model2)

change = get_change_for_type(
field.verbose_name,
(value1, value2),
field,
)

return change


def get_changes_between_models(model1, model2, excludes=[]):
changes = []
changes = {}

for field in model1._meta.fields:
if not (isinstance(field, (fields.AutoField,
fields.related.RelatedField,
))
or field.name in excludes):

value2 = field.value_from_object(model2)
value1 = field.value_from_object(model1)

change = get_change_for_type(
field.verbose_name,
(value1, value2),
field)

changes.append(change)
):

if field.name in excludes:
continue

name = u"%s__%s" % (model1.__class__.__name__.lower(), field.name)

changes[name] = get_change(model1, model2, field)


return changes



def get_diff_operations(a, b):
operations = []
sequence_matcher = difflib.SequenceMatcher(None, a, b)
Expand Down
2 changes: 0 additions & 2 deletions src/moderation/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist

from moderation.diff import get_changes_between_models


class MetaClass(type):

Expand Down
19 changes: 9 additions & 10 deletions src/moderation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,17 @@ def _moderate(self, status, moderated_by, reason):
self.moderator.inform_user(self.content_object, self.changed_by)

def has_object_been_changed(self, oryginal_obj):
from django.db.models import fields
changes = {}
for field in oryginal_obj._meta.fields:
if not (isinstance(field, (fields.AutoField,
fields.related.RelatedField,
))):
value2 = unicode(field.value_from_object(oryginal_obj))
value1 = unicode(field.value_from_object(self.changed_object))
if value1 != value2:
return True
changes = get_changes_between_models(oryginal_obj,
self.changed_object,
self.moderator.fields_exclude)

for change in changes:
left_change, right_change = changes[change].change
if left_change != right_change:
return True

return False


def approve(self, moderated_by=None, reason=None):
pre_moderation.send(sender=self.content_object.__class__,
Expand Down
3 changes: 3 additions & 0 deletions src/moderation/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
from moderation.tests.test_forms import *
from moderation.tests.test_generic_moderator import *
from moderation.tests.regresion_tests import *
from moderation.tests.acceptance.exclude import *


3 changes: 3 additions & 0 deletions src/moderation/tests/acceptance/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'''
Package contains acceptance tests for django-moderation
'''
106 changes: 106 additions & 0 deletions src/moderation/tests/acceptance/exclude.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse

from moderation import ModerationManager, GenericModerator
from moderation.tests.test_app.models import UserProfile
from moderation.tests.utils.testsettingsmanager import SettingsTestCase
from moderation.tests.utils import setup_moderation, teardown_moderation
from moderation.diff import get_changes_between_models



class ExcludeAcceptanceTestCase(SettingsTestCase):
'''
As developer i want to have way to ignore/exclude model fields from
moderation
'''
fixtures = ['test_users.json', 'test_moderation.json']
test_settings = 'moderation.tests.settings.generic'
urls = 'moderation.tests.test_urls'

def setUp(self):
self.moderation, self.old_moderation = setup_moderation()

self.client.login(username='admin', password='aaaa')

class UserProfileModerator(GenericModerator):
fields_exclude = ['url']

self.moderation.register(UserProfile, UserProfileModerator)

def tearDown(self):
teardown_moderation(self.moderation, self.old_moderation,
[UserProfile])

def test_excluded_field_shoud_not_be_moderated_when_object_is_edited(self):
'''
Change field that is excluded from moderation,
go to moderation admin
'''
profile = UserProfile.objects.get(user__username='moderator')

profile.url = 'http://dominno.pl'

profile.save()

url = reverse('admin:moderation_moderatedobject_change',
args=(profile.moderated_object.pk,))

response = self.client.get(url, {})

changes = response.context['changes']

self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].change, ('Old description',
'Old description'))

def test_non_excluded_field_shoud_be_moderated_when_object_is_edited(self):
'''
Change field that is not excluded from moderation,
go to moderation admin
'''
profile = UserProfile.objects.get(user__username='moderator')

profile.description = 'New description'

profile.save()

url = reverse('admin:moderation_moderatedobject_change',
args=(profile.moderated_object.pk,))


response = self.client.get(url, {})

changes = response.context['changes']

self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].change, ('Old description',
'New description'))

def test_excluded_field_shoud_not_be_moderated_when_object_is_created(self):
'''
Create new object, only non excluded fields are used by moderation system
'''
profile = UserProfile(description='Profile for new user',
url='http://www.dominno.com',
user=User.objects.get(username='user1'))
profile.save()

url = reverse('admin:moderation_moderatedobject_change',
args=(profile.moderated_object.pk,))


response = self.client.get(url, {})

changes = response.context['changes']

self.assertEqual(len(changes), 1)
self.assertEqual(changes[0].change, ('Profile for new user',
'Profile for new user'))







36 changes: 28 additions & 8 deletions src/moderation/tests/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ def test_get_changes_between_models(self):
self.profile)

self.assertEqual(unicode(changes),
u'[Change object: New description - Old description,'\
u' Change object: http://www.google.com - '\
u'http://www.google.com]')
u"{u'userprofile__url': Change object: http://www.google.com"\
u" - http://www.google.com, u'userprofile__description': "\
u"Change object: New description - Old description}")

def test_get_changes_between_models_image(self):
'''Verify proper diff for ImageField fields'''
Expand All @@ -104,10 +104,25 @@ def test_get_changes_between_models_image(self):
image2.save()

changes = get_changes_between_models(image1, image2)
self.assertEqual(changes[0].diff,
self.assertEqual(changes['modelwithimage__image'].diff,
u'<img src="/media/tmp/test1.jpg">'\
u'<img style="margin-left: 10px;" '\
u'src="/media/tmp/test2.jpg">')

def test_excluded_fields_should_be_excluded_from_changes(self):
self.profile.description = 'New description'
moderated_object = ModeratedObject(content_object=self.profile)
moderated_object.save()

self.profile = UserProfile.objects.get(user__username='moderator')

changes = get_changes_between_models(moderated_object.changed_object,
self.profile, excludes=['description'])

self.assertEqual(unicode(changes),
u"{u'userprofile__url': Change object: "\
u"http://www.google.com - http://www.google.com}")



class DiffTestCase(unittest.TestCase):
Expand Down Expand Up @@ -169,17 +184,22 @@ def test_date_field_in_model_object_should_be_unicode(self):
changes between models, all changes should be unicode.
'''
changes = get_changes_between_models(self.obj1, self.obj2)
self.assertTrue(isinstance(changes[0].change[0], unicode))
self.assertTrue(isinstance(changes[0].change[1], unicode))

date_change = changes['modelwithdatefield__date']

self.assertTrue(isinstance(date_change.change[0], unicode))
self.assertTrue(isinstance(date_change.change[1], unicode))

def test_html_to_list_should_return_list(self):
'''Test if changes dict generated from model that has non unicode field
is properly used by html_to_list function
'''
changes = get_changes_between_models(self.obj1, self.obj2)

changes_list1 = html_to_list(changes[0].change[0])
changes_list2 = html_to_list(changes[0].change[1])
date_change = changes['modelwithdatefield__date']

changes_list1 = html_to_list(date_change.change[0])
changes_list2 = html_to_list(date_change.change[1])

self.assertTrue(isinstance(changes_list1, list))
self.assertTrue(isinstance(changes_list2, list))
3 changes: 3 additions & 0 deletions src/moderation/tests/test_generic_moderator.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ def test_inform_user(self):
self.moderator = GenericModerator(UserProfile)
self.moderator.inform_user(self.user, self.user)
self.assertEqual(len(mail.outbox), 1)

def test_moderator_should_have_field_exclude(self):
self.assertTrue(hasattr(self.moderator, 'fields_exclude'))


class AutoModerateModeratorTestCase(TestCase):
Expand Down
3 changes: 3 additions & 0 deletions src/moderation/tests/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class CustomManager(Manager):
self.moderation._remove_fields(moderator)

def test_get_or_create_moderated_object_exist(self):
self.moderation.register(UserProfile)
profile = UserProfile.objects.get(user__username='moderator')

ModeratedObject(content_object=profile).save()
Expand All @@ -312,6 +313,8 @@ def test_get_or_create_moderated_object_exist(self):
self.assertNotEqual(object.pk, None)
self.assertEqual(object.changed_object.description,
u'Old description')

self.moderation.unregister(UserProfile)

def test_get_or_create_moderated_object_does_not_exist(self):
profile = UserProfile.objects.get(user__username='moderator')
Expand Down

0 comments on commit b3c3122

Please sign in to comment.