Skip to content

Commit

Permalink
Adding smarter validation to relationship status objects on save, to
Browse files Browse the repository at this point in the history
prevent slug collisions between different status slugs.  Migrating slug
fields to char fields so 'slug' validation won't occur, allowing
statuses to be made invisible by giving them an invalid slug. Also added
RelationshipStatus to the admin.
  • Loading branch information
coleifer committed Apr 17, 2010
1 parent f91b3f6 commit 79531ca
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 4 deletions.
Binary file modified example/example.db
Binary file not shown.
8 changes: 7 additions & 1 deletion relationships/admin.py
Expand Up @@ -2,7 +2,8 @@
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin

from relationships.models import Relationship
from relationships.forms import RelationshipStatusAdminForm
from relationships.models import Relationship, RelationshipStatus

class RelationshipInline(admin.TabularInline):
model = Relationship
Expand All @@ -13,5 +14,10 @@ class RelationshipInline(admin.TabularInline):
class UserRelationshipAdmin(UserAdmin):
inlines = (RelationshipInline,)


class RelationshipStatusAdmin(admin.ModelAdmin):
form = RelationshipStatusAdminForm

admin.site.unregister(User)
admin.site.register(User, UserRelationshipAdmin)
admin.site.register(RelationshipStatus, RelationshipStatusAdmin)
39 changes: 39 additions & 0 deletions relationships/forms.py
@@ -0,0 +1,39 @@
from django import forms
from django.db.models import Q
from relationships.models import RelationshipStatus

class RelationshipStatusAdminForm(forms.ModelForm):
class Meta:
model = RelationshipStatus

def duplicate_slug_check(self, status_slug):
status_qs = RelationshipStatus.objects.filter(
Q(from_slug=status_slug) |
Q(to_slug=status_slug) |
Q(symmetrical_slug=status_slug))
if self.instance.pk:
status_qs = status_qs.exclude(pk=self.instance.pk)
if status_qs.count() > 0:
raise forms.ValidationError('"%s" slug already in use on %s' % \
(status_slug, unicode(status_qs[0])))

def clean_from_slug(self):
self.duplicate_slug_check(self.cleaned_data['from_slug'])
return self.cleaned_data['from_slug']

def clean_to_slug(self):
self.duplicate_slug_check(self.cleaned_data['to_slug'])
return self.cleaned_data['to_slug']

def clean_symmetrical_slug(self):
self.duplicate_slug_check(self.cleaned_data['symmetrical_slug'])
return self.cleaned_data['symmetrical_slug']

def clean(self):
if self.errors:
return self.cleaned_data
if self.cleaned_data['from_slug'] == self.cleaned_data['to_slug'] or \
self.cleaned_data['to_slug'] == self.cleaned_data['symmetrical_slug'] or \
self.cleaned_data['symmetrical_slug'] == self.cleaned_data['from_slug']:
raise forms.ValidationError('from, to, and symmetrical slugs cannot be the same')
return self.cleaned_data
121 changes: 121 additions & 0 deletions relationships/migrations/0003_slugs_to_charfields.py
@@ -0,0 +1,121 @@

from south.db import db
from django.db import models
from relationships.models import *

class Migration:

def forwards(self, orm):

# Deleting unique_together for [symmetrical_slug] on relationshipstatus.
db.delete_unique('relationships_relationshipstatus', ['symmetrical_slug'])

# Deleting unique_together for [from_slug] on relationshipstatus.
db.delete_unique('relationships_relationshipstatus', ['from_slug'])

# Deleting unique_together for [to_slug] on relationshipstatus.
db.delete_unique('relationships_relationshipstatus', ['to_slug'])

# Changing field 'RelationshipStatus.to_slug'
# (to signature: django.db.models.fields.CharField(max_length=100))
db.alter_column('relationships_relationshipstatus', 'to_slug', orm['relationships.relationshipstatus:to_slug'])

# Changing field 'RelationshipStatus.symmetrical_slug'
# (to signature: django.db.models.fields.CharField(max_length=100))
db.alter_column('relationships_relationshipstatus', 'symmetrical_slug', orm['relationships.relationshipstatus:symmetrical_slug'])

# Changing field 'RelationshipStatus.from_slug'
# (to signature: django.db.models.fields.CharField(max_length=100))
db.alter_column('relationships_relationshipstatus', 'from_slug', orm['relationships.relationshipstatus:from_slug'])



def backwards(self, orm):

# Changing field 'RelationshipStatus.to_slug'
# (to signature: django.db.models.fields.SlugField(max_length=50, unique=True, db_index=True))
db.alter_column('relationships_relationshipstatus', 'to_slug', orm['relationships.relationshipstatus:to_slug'])

# Changing field 'RelationshipStatus.symmetrical_slug'
# (to signature: django.db.models.fields.SlugField(max_length=50, unique=True, db_index=True))
db.alter_column('relationships_relationshipstatus', 'symmetrical_slug', orm['relationships.relationshipstatus:symmetrical_slug'])

# Changing field 'RelationshipStatus.from_slug'
# (to signature: django.db.models.fields.SlugField(max_length=50, unique=True, db_index=True))
db.alter_column('relationships_relationshipstatus', 'from_slug', orm['relationships.relationshipstatus:from_slug'])

# Creating unique_together for [to_slug] on relationshipstatus.
db.create_unique('relationships_relationshipstatus', ['to_slug'])

# Creating unique_together for [from_slug] on relationshipstatus.
db.create_unique('relationships_relationshipstatus', ['from_slug'])

# Creating unique_together for [symmetrical_slug] on relationshipstatus.
db.create_unique('relationships_relationshipstatus', ['symmetrical_slug'])



models = {
'auth.group': {
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'unique_together': "(('content_type', 'codename'),)"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'relationships': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'relationships.relationship': {
'Meta': {'unique_together': "(('from_user', 'to_user', 'status'),)"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'from_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_users'", 'to': "orm['auth.User']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'site': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': "orm['sites.Site']"}),
'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['relationships.RelationshipStatus']"}),
'to_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'to_users'", 'to': "orm['auth.User']"})
},
'relationships.relationshipstatus': {
'from_slug': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'private': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'symmetrical_slug': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'to_slug': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'sites.site': {
'Meta': {'db_table': "'django_site'"},
'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}
}

complete_apps = ['relationships']
7 changes: 4 additions & 3 deletions relationships/models.py
Expand Up @@ -18,11 +18,11 @@ def blocking(self):
class RelationshipStatus(models.Model):
name = models.CharField(max_length=100)
verb = models.CharField(max_length=100)
from_slug = models.SlugField(unique=True,
from_slug = models.CharField(max_length=100,
help_text="Denote the relationship from the user, i.e. 'following'")
to_slug = models.SlugField(unique=True,
to_slug = models.CharField(max_length=100,
help_text="Denote the relationship to the user, i.e. 'followers'")
symmetrical_slug = models.SlugField(unique=True,
symmetrical_slug = models.CharField(max_length=100,
help_text="When a mutual relationship exists, i.e. 'friends'")
login_required = models.BooleanField(default=False,
help_text="Users must be logged in to see these relationships")
Expand All @@ -33,6 +33,7 @@ class RelationshipStatus(models.Model):

class Meta:
ordering = ('name',)
verbose_name_plural = 'Relationship statuses'

def __unicode__(self):
return self.name
Expand Down
65 changes: 65 additions & 0 deletions relationships/tests.py
@@ -1,6 +1,8 @@
from django import forms
from django.test import TestCase
from django.contrib.auth.models import User
from django.template import Template, Context
from relationships.forms import RelationshipStatusAdminForm
from relationships.models import Relationship, RelationshipStatus

class RelationshipsTestCase(TestCase):
Expand Down Expand Up @@ -294,3 +296,66 @@ def test_if_relationship_tag(self):
c = Context({'john': self.john, 'yoko': self.yoko})
rendered = t.render(c)
self.assertEqual(rendered, 'y')

class RelationshipStatusAdminFormTestCase(TestCase):
fixtures = ['relationships.json']

def setUp(self):
self.walrus = User.objects.get(username='The_Walrus')
self.john = User.objects.get(username='John')
self.paul = User.objects.get(username='Paul')
self.yoko = User.objects.get(username='Yoko')
self.following = RelationshipStatus.objects.get(from_slug='following')
self.blocking = RelationshipStatus.objects.get(from_slug='blocking')

def test_no_dupes(self):
payload = {
'name': 'Testing',
'verb': 'testing',
'from_slug': 'testing',
'to_slug': 'testers',
'symmetrical_slug': 'tests'
}
form = RelationshipStatusAdminForm(payload)
self.assertTrue(form.is_valid())
test_status = form.save()

# saving again should work
form = RelationshipStatusAdminForm(payload, instance=test_status)
self.assertTrue(form.is_valid())

payload['from_slug'] = 'testers'
payload['to_slug'] = 'testing'

# saving will work since it will not test against the current instance
form = RelationshipStatusAdminForm(payload, instance=test_status)
self.assertTrue(form.is_valid())

# setting the from_slug to the to_slug will raise an error
payload['from_slug'] = 'testers'
payload['to_slug'] = 'testers'
form = RelationshipStatusAdminForm(payload, instance=test_status)
self.assertFalse(form.is_valid())

# setting the from_slug to the symmetrical_slug will raise an error
payload['from_slug'] = 'tests'
form = RelationshipStatusAdminForm(payload, instance=test_status)
self.assertFalse(form.is_valid())

# setting to a pre-existing from_slug will fail
payload['from_slug'] = 'following'
form = RelationshipStatusAdminForm(payload)
self.assertFalse(form.is_valid())
self.assertTrue('from_slug' in form.errors)

# setting the from_slug to a pre-existing to_slug will also fail
payload['from_slug'] = 'followers'
form = RelationshipStatusAdminForm(payload)
self.assertFalse(form.is_valid())
self.assertTrue('from_slug' in form.errors)

# setting the from_slug to a pre-existing symetrical_slug will also fail
payload['from_slug'] = 'friends'
form = RelationshipStatusAdminForm(payload)
self.assertFalse(form.is_valid())
self.assertTrue('from_slug' in form.errors)

0 comments on commit 79531ca

Please sign in to comment.