diff --git a/README.rst b/README.rst index 3bfb04a..5579695 100644 --- a/README.rst +++ b/README.rst @@ -54,3 +54,10 @@ Installation OR put the ``relationships`` folder on your python-path + + +TODO +---- +* [X] Make `if_relationship` a function in template instead a `if` alternative +* Add full patch for `django` 2.0. + diff --git a/relationships/__init__.py b/relationships/__init__.py index 2300c84..a69b162 100644 --- a/relationships/__init__.py +++ b/relationships/__init__.py @@ -1 +1,2 @@ -VERSION = (0, 3, 3) +VERSION = (0, 4, 0) +default_app_config = 'relationships.apps.RelationshipsConfig' diff --git a/relationships/admin.py b/relationships/admin.py index 2e2bdfa..d926e10 100644 --- a/relationships/admin.py +++ b/relationships/admin.py @@ -1,25 +1,41 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.models import User as DefaultUserModel -from .compat import User -from .forms import RelationshipStatusAdminForm -from .models import Relationship, RelationshipStatus +from relationships.compat import User +from relationships.forms import RelationshipStatusAdminForm +from relationships.models import ( + Relationship, + RelationshipStatus +) class RelationshipInline(admin.TabularInline): model = Relationship - raw_id_fields = ('from_user', 'to_user') + raw_id_fields = ['from_user', 'to_user'] extra = 1 fk_name = 'from_user' -class UserRelationshipAdmin(UserAdmin): - inlines = (RelationshipInline,) +class UserRelationshipAdminMixin(object): + inlines = [RelationshipInline, ] class RelationshipStatusAdmin(admin.ModelAdmin): form = RelationshipStatusAdminForm -admin.site.unregister(User) -admin.site.register(User, UserRelationshipAdmin) + list_display = ['name', 'verb', 'from_slug', 'to_slug', 'symmetrical_slug'] + + +if User == DefaultUserModel: + class UserRelationshipAdmin(UserRelationshipAdminMixin, UserAdmin): + pass + + + try: + admin.site.unregister(User) + except admin.sites.NotRegistered: + pass + admin.site.register(User, UserRelationshipAdmin) + admin.site.register(RelationshipStatus, RelationshipStatusAdmin) diff --git a/relationships/apps.py b/relationships/apps.py new file mode 100644 index 0000000..a2d077e --- /dev/null +++ b/relationships/apps.py @@ -0,0 +1,29 @@ +from django.apps import AppConfig + + +class RelationshipsConfig(AppConfig): + name = 'relationships' + + def ready(self): + from django.contrib.auth import get_user_model + User = get_user_model() + + from relationships import compat + compat.User = User + + from relationships import models as rmodels + rmodels.User = User + + from django.db.models import ManyToManyField + from relationships.models import ( + RelationshipsDescriptor, + Relationship + ) + field = ManyToManyField( + User, + through=Relationship, + symmetrical=False, + related_name='related_to' + ) + field.contribute_to_class(User, 'relationships') + setattr(User, 'relationships', RelationshipsDescriptor()) diff --git a/relationships/compat.py b/relationships/compat.py index b9f7e96..ccca252 100644 --- a/relationships/compat.py +++ b/relationships/compat.py @@ -1,18 +1,290 @@ import django +try: + from django.contrib.auth.models import User +except ImportError: + raise ImportError(u"User model is not to be found.") -# Django 1.5 add support for custom auth user model -if django.VERSION >= (1, 5): - from django.contrib.auth import get_user_model - User = get_user_model() +if django.VERSION <= (2, 0): + from django.conf.urls import ( + include, + url + ) else: - try: - from django.contrib.auth.models import User - except ImportError: - raise ImportError(u"User model is not to be found.") + from django.urls import include + from django.urls import path as url + # TODO: It requires more changes -# location of patterns, url, include changes in 1.4 onwards +""" try: - from django.conf.urls import patterns, url, include -except: - from django.conf.urls.defaults import patterns, url, include + from django.db.models.fields.related import create_many_related_manager +except ImportError: + from django.db.models.fields.related_descriptors import \ + create_forward_many_to_many_manager as create_many_related_manager +""" + + +####Hack!!!! direct copy from django 1.4.7 package##### + +def create_many_related_manager(superclass, rel): + """ + Creates a manager that subclasses 'superclass' (which is a Manager) + and adds behavior for many-to-many related objects. + """ + + class ManyRelatedManager(superclass): + def __init__(self, model=None, query_field_name=None, instance=None, symmetrical=None, + source_field_name=None, target_field_name=None, reverse=False, + through=None, prefetch_cache_name=None): + super(ManyRelatedManager, self).__init__() + self.model = model + self.query_field_name = query_field_name + self.core_filters = {'%s__pk' % query_field_name: instance._get_pk_val()} + self.instance = instance + self.symmetrical = symmetrical + self.source_field_name = source_field_name + self.target_field_name = target_field_name + self.reverse = reverse + self.through = through + self.prefetch_cache_name = prefetch_cache_name + self._fk_val = self._get_fk_val(instance, source_field_name) + if self._fk_val is None: + raise ValueError( + '"{0!r}" needs to have a value for field "{0!s}" before ' + 'this many-to-many relationship can be used.'.format(instance, source_field_name) + ) + # Even if this relation is not to pk, we require still pk value. + # The wish is that the instance has been already saved to DB, + # although having a pk value isn't a guarantee of that. + if instance.pk is None: + raise ValueError( + "{0!r} instance needs to have a primary key value before " + "a many-to-many relationship can be used.".format(instance.__class__.__name__) + ) + + def _get_fk_val(self, obj, field_name): + """ + Returns the correct value for this relationship's foreign key. This + might be something else than pk value when to_field is used. + """ + if not self.through: + # Make custom m2m fields with no through model defined usable. + return obj.pk + fk = self.through._meta.get_field(field_name) + if fk.rel.field_name and fk.rel.field_name != fk.rel.to._meta.pk.attname: + attname = fk.rel.get_related_field().get_attname() + return fk.get_prep_lookup('exact', getattr(obj, attname)) + else: + return obj.pk + + def get_query_set(self): + try: + return self.instance._prefetched_objects_cache[self.prefetch_cache_name] + except (AttributeError, KeyError): + db = self._db or router.db_for_read(self.instance.__class__, instance=self.instance) + return super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter( + **self.core_filters) + + def get_prefetch_query_set(self, instances): + instance = instances[0] + from django.db import connections + db = self._db or router.db_for_read(instance.__class__, instance=instance) + query = {'%s__pk__in' % self.query_field_name: + set(obj._get_pk_val() for obj in instances)} + qs = super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter(**query) + + # M2M: need to annotate the query in order to get the primary model + # that the secondary model was actually related to. We know that + # there will already be a join on the join table, so we can just add + # the select. + + # For non-autocreated 'through' models, can't assume we are + # dealing with PK values. + fk = self.through._meta.get_field(self.source_field_name) + source_col = fk.column + join_table = self.through._meta.db_table + connection = connections[db] + qn = connection.ops.quote_name + qs = qs.extra(select={'_prefetch_related_val': + '%s.%s' % (qn(join_table), qn(source_col))}) + select_attname = fk.rel.get_related_field().get_attname() + return (qs, + attrgetter('_prefetch_related_val'), + attrgetter(select_attname), + False, + self.prefetch_cache_name) + + # If the ManyToMany relation has an intermediary model, + # the add and remove methods do not exist. + if rel.through._meta.auto_created: + def add(self, *objs): + self._add_items(self.source_field_name, self.target_field_name, *objs) + + # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table + if self.symmetrical: + self._add_items(self.target_field_name, self.source_field_name, *objs) + + add.alters_data = True + + def remove(self, *objs): + self._remove_items(self.source_field_name, self.target_field_name, *objs) + + # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table + if self.symmetrical: + self._remove_items(self.target_field_name, self.source_field_name, *objs) + + remove.alters_data = True + + def clear(self): + self._clear_items(self.source_field_name) + + # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table + if self.symmetrical: + self._clear_items(self.target_field_name) + + clear.alters_data = True + + def create(self, **kwargs): + # This check needs to be done here, since we can't later remove this + # from the method lookup table, as we do with add and remove. + if not self.through._meta.auto_created: + opts = self.through._meta + raise AttributeError( + "Cannot use create() on a ManyToManyField which specifies an intermediary model. " + "Use {0!s}.{0!s}'s Manager instead.".format(opts.app_label, opts.object_name) + ) + db = router.db_for_write(self.instance.__class__, instance=self.instance) + new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs) + self.add(new_obj) + return new_obj + + create.alters_data = True + + def get_or_create(self, **kwargs): + db = router.db_for_write(self.instance.__class__, instance=self.instance) + obj, created = \ + super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs) + # We only need to add() if created because if we got an object back + # from get() then the relationship already exists. + if created: + self.add(obj) + return obj, created + + get_or_create.alters_data = True + + def _add_items(self, source_field_name, target_field_name, *objs): + # source_field_name: the PK fieldname in join table for the source object + # target_field_name: the PK fieldname in join table for the target object + # *objs - objects to add. Either object instances, or primary keys of object instances. + + # If there aren't any objects, there is nothing to do. + from django.db.models import Model + if objs: + new_ids = set() + for obj in objs: + if isinstance(obj, self.model): + if not router.allow_relation(obj, self.instance): + raise ValueError( + 'Cannot add "{0!r}": instance is on database "{0!s}", value is on database "{0!s}"'.format( + obj, self.instance._state.db, + obj._state.db + ) + ) + fk_val = self._get_fk_val(obj, target_field_name) + if fk_val is None: + raise ValueError( + 'Cannot add "{0!r}": the value for field "{0!s}" is None'.format(obj, target_field_name) + ) + new_ids.add(self._get_fk_val(obj, target_field_name)) + elif isinstance(obj, Model): + raise TypeError( + "'{0!s}' instance expected, got {0!r}".format(self.model._meta.object_name, obj)) + else: + new_ids.add(obj) + db = router.db_for_write(self.through, instance=self.instance) + vals = self.through._default_manager.using(db).values_list(target_field_name, flat=True) + vals = vals.filter(**{ + source_field_name: self._fk_val, + '%s__in' % target_field_name: new_ids, + }) + new_ids = new_ids - set(vals) + + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are inserting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=self.through, action='pre_add', + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=new_ids, using=db) + # Add the ones that aren't there already + self.through._default_manager.using(db).bulk_create([ + self.through(**{ + '%s_id' % source_field_name: self._fk_val, + '%s_id' % target_field_name: obj_id, + }) + for obj_id in new_ids + ]) + + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are inserting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send( + sender=self.through, action='post_add', + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=new_ids, using=db + ) + + def _remove_items(self, source_field_name, target_field_name, *objs): + # source_field_name: the PK colname in join table for the source object + # target_field_name: the PK colname in join table for the target object + # *objs - objects to remove + + # If there aren't any objects, there is nothing to do. + if objs: + # Check that all the objects are of the right type + old_ids = set() + for obj in objs: + if isinstance(obj, self.model): + old_ids.add(self._get_fk_val(obj, target_field_name)) + else: + old_ids.add(obj) + # Work out what DB we're operating on + db = router.db_for_write(self.through, instance=self.instance) + # Send a signal to the other end if need be. + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are deleting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=self.through, action="pre_remove", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=old_ids, using=db) + # Remove the specified objects from the join table + self.through._default_manager.using(db).filter(**{ + source_field_name: self._fk_val, + '%s__in' % target_field_name: old_ids + }).delete() + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are deleting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=self.through, action="post_remove", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=old_ids, using=db) + + def _clear_items(self, source_field_name): + db = router.db_for_write(self.through, instance=self.instance) + # source_field_name: the PK colname in join table for the source object + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are clearing the + # duplicate data rows for symmetrical reverse entries. + signals.m2m_changed.send(sender=self.through, action="pre_clear", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=None, using=db) + self.through._default_manager.using(db).filter(**{ + source_field_name: self._fk_val + }).delete() + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are clearing the + # duplicate data rows for symmetrical reverse entries. + signals.m2m_changed.send(sender=self.through, action="post_clear", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=None, using=db) + + return ManyRelatedManager diff --git a/relationships/decorators.py b/relationships/decorators.py index e558856..fb5551b 100644 --- a/relationships/decorators.py +++ b/relationships/decorators.py @@ -1,10 +1,11 @@ from django.shortcuts import get_object_or_404 -from .compat import User +from relationships.compat import User def require_user(view): def inner(request, username, *args, **kwargs): user = get_object_or_404(User, username=username) return view(request, user, *args, **kwargs) + return inner diff --git a/relationships/fixtures/initial_data.json b/relationships/fixtures/initial_data.json index 30c0506..544ba10 100644 --- a/relationships/fixtures/initial_data.json +++ b/relationships/fixtures/initial_data.json @@ -1,6 +1,5 @@ [ { - "pk": 1, "model": "relationships.relationshipstatus", "fields": { "name": "Following", @@ -11,7 +10,6 @@ } }, { - "pk": 2, "model": "relationships.relationshipstatus", "fields": { "name": "Blocking", diff --git a/relationships/forms.py b/relationships/forms.py index 264f99b..37db188 100644 --- a/relationships/forms.py +++ b/relationships/forms.py @@ -1,12 +1,14 @@ from django import forms from django.db.models import Q +from django.utils.translation import ugettext as _ -from .models import RelationshipStatus +from relationships.models import RelationshipStatus class RelationshipStatusAdminForm(forms.ModelForm): class Meta: model = RelationshipStatus + fields = '__all__' def duplicate_slug_check(self, status_slug): status_qs = RelationshipStatus.objects.filter( @@ -19,28 +21,27 @@ def duplicate_slug_check(self, status_slug): status_qs = status_qs.exclude(pk=self.instance.pk) if status_qs.exists(): - raise forms.ValidationError('"%s" slug already in use on %s' % - (status_slug, unicode(status_qs[0]))) + raise forms.ValidationError(_('"{0}" slug already in use on {1}').format(status_slug, str(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_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_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_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 + 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 must be different') + 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 must be different')) - return self.cleaned_data + return self.cleaned_data diff --git a/relationships/listeners.py b/relationships/listeners.py index e315143..5e58dec 100644 --- a/relationships/listeners.py +++ b/relationships/listeners.py @@ -1,6 +1,9 @@ from django.db.models import signals -from .models import RelationshipStatus, Relationship +from relationships.models import ( + RelationshipStatus, + Relationship +) def mutually_exclusive_fix(sender, instance, created, **kwargs): diff --git a/relationships/migrations/0001_initial.py b/relationships/migrations/0001_initial.py index 515c651..9b0605f 100644 --- a/relationships/migrations/0001_initial.py +++ b/relationships/migrations/0001_initial.py @@ -1,102 +1,67 @@ -from south.db import db -from relationships.models import * +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from django.db import models, migrations +from django.conf import settings -class Migration: - def forwards(self, orm): +class Migration(migrations.Migration): - # Adding model 'RelationshipStatus' - db.create_table('relationships_relationshipstatus', ( - ('id', orm['relationships.RelationshipStatus:id']), - ('name', orm['relationships.RelationshipStatus:name']), - ('verb', orm['relationships.RelationshipStatus:verb']), - ('from_slug', orm['relationships.RelationshipStatus:from_slug']), - ('to_slug', orm['relationships.RelationshipStatus:to_slug']), - ('symmetrical_slug', orm['relationships.RelationshipStatus:symmetrical_slug']), - ('login_required', orm['relationships.RelationshipStatus:login_required']), - ('private', orm['relationships.RelationshipStatus:private']), - )) - db.send_create_signal('relationships', ['RelationshipStatus']) + dependencies = [ + ('sites', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] - # Adding model 'Relationship' - db.create_table('relationships_relationship', ( - ('id', orm['relationships.Relationship:id']), - ('from_user', orm['relationships.Relationship:from_user']), - ('to_user', orm['relationships.Relationship:to_user']), - ('status', orm['relationships.Relationship:status']), - ('created', orm['relationships.Relationship:created']), - )) - db.send_create_signal('relationships', ['Relationship']) - - # Creating unique_together for [from_user, to_user, status] on Relationship. - db.create_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id']) - - def backwards(self, orm): - - # Deleting unique_together for [from_user, to_user, status] on Relationship. - db.delete_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id']) - - # Deleting model 'RelationshipStatus' - db.delete_table('relationships_relationshipstatus') - - # Deleting model 'Relationship' - db.delete_table('relationships_relationship') - - 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']", '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']", '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']", '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'}), - '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.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - '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.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - 'to_slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - 'verb': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['relationships'] + operations = [ + migrations.CreateModel( + name='Relationship', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='created')), + ('weight', models.FloatField(default=1.0, null=True, verbose_name='weight', blank=True)), + ('from_user', models.ForeignKey(related_name='from_users', verbose_name='from user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ('site', models.ForeignKey(related_name='relationships', default=1, verbose_name='site', to='sites.Site', on_delete=models.CASCADE)), + ], + options={ + 'ordering': ('created',), + 'verbose_name': 'Relationship', + 'verbose_name_plural': 'Relationships', + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='RelationshipStatus', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100, verbose_name='name')), + ('verb', models.CharField(max_length=100, verbose_name='verb')), + ('from_slug', models.CharField(help_text="Denote the relationship from the user, i.e. 'following'", max_length=100, verbose_name='from slug')), + ('to_slug', models.CharField(help_text="Denote the relationship to the user, i.e. 'followers'", max_length=100, verbose_name='to slug')), + ('symmetrical_slug', models.CharField(help_text="When a mutual relationship exists, i.e. 'friends'", max_length=100, verbose_name='symmetrical slug')), + ('login_required', models.BooleanField(default=False, help_text='Users must be logged in to see these relationships', verbose_name='login required')), + ('private', models.BooleanField(default=False, help_text='Only the user who owns these relationships can see them', verbose_name='private')), + ], + options={ + 'ordering': ('name',), + 'verbose_name': 'Relationship status', + 'verbose_name_plural': 'Relationship statuses', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='relationship', + name='status', + field=models.ForeignKey(verbose_name='status', to='relationships.RelationshipStatus', on_delete=models.CASCADE), + preserve_default=True, + ), + migrations.AddField( + model_name='relationship', + name='to_user', + field=models.ForeignKey(related_name='to_users', verbose_name='to user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='relationship', + unique_together=set([('from_user', 'to_user', 'status', 'site')]), + ), + ] diff --git a/relationships/migrations/0002_add_site_fk.py b/relationships/migrations/0002_add_site_fk.py deleted file mode 100644 index 7a083b8..0000000 --- a/relationships/migrations/0002_add_site_fk.py +++ /dev/null @@ -1,80 +0,0 @@ -from south.db import db -from relationships.models import * - - -class Migration: - - def forwards(self, orm): - - # Adding field 'Relationship.site' - db.add_column('relationships_relationship', 'site', orm['relationships.relationship:site']) - - def backwards(self, orm): - - # Deleting field 'Relationship.site' - db.delete_column('relationships_relationship', 'site_id') - - 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']", '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']", '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']", '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.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - '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.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - 'to_slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}), - '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'] diff --git a/relationships/migrations/0002_relationship_updated_at.py b/relationships/migrations/0002_relationship_updated_at.py new file mode 100644 index 0000000..43c1b21 --- /dev/null +++ b/relationships/migrations/0002_relationship_updated_at.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('relationships', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='relationship', + name='updated_at', + field=models.DateTimeField(default=datetime.datetime(2015, 2, 8, 3, 42, 38, 33802, tzinfo=utc), verbose_name='updated_at', auto_now=True), + preserve_default=False, + ), + ] diff --git a/relationships/migrations/0003_data_relationship_status_v1.py b/relationships/migrations/0003_data_relationship_status_v1.py new file mode 100644 index 0000000..5f96772 --- /dev/null +++ b/relationships/migrations/0003_data_relationship_status_v1.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + +def create_relationship_status(apps, schema_editor): + + RelationshipStatus = apps.get_model('relationships', "RelationshipStatus") + + rs = RelationshipStatus.objects.filter(pk=1) + if(rs.exists()): + rs = rs[0] + rs.name = 'Following' + rs.to_slug = 'followers' + rs.from_slug = 'following' + rs.symmetrical_slug = 'friends' + rs.verb = 'follow' + rs.save() + else: + RelationshipStatus.objects.create( + name='Following', + to_slug='followers', + from_slug='following', + symmetrical_slug='friends', + verb='follow', + ) + + rs = RelationshipStatus.objects.filter(pk=2) + if(rs.exists()): + rs = rs[0] + rs.name = 'Blocking' + rs.to_slug = 'blockers' + rs.from_slug = 'blocking' + rs.symmetrical_slug = '!' + rs.verb = 'block' + rs.login_required = True + rs.private = True + rs.save() + else: + RelationshipStatus.objects.create( + name='Blocking', + to_slug='blockers', + from_slug='blocking', + symmetrical_slug='!', + verb='block', + login_required=True, + private=True, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('relationships', '0002_relationship_updated_at'), + ] + + operations = [ + migrations.RunPython(create_relationship_status), + ] diff --git a/relationships/migrations/0003_slugs_to_charfields.py b/relationships/migrations/0003_slugs_to_charfields.py deleted file mode 100644 index 241d3c8..0000000 --- a/relationships/migrations/0003_slugs_to_charfields.py +++ /dev/null @@ -1,116 +0,0 @@ -from south.db import db -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'] diff --git a/relationships/migrations/0004_add_site_to_unique_together.py b/relationships/migrations/0004_add_site_to_unique_together.py deleted file mode 100644 index 4deff5a..0000000 --- a/relationships/migrations/0004_add_site_to_unique_together.py +++ /dev/null @@ -1,86 +0,0 @@ -from south.db import db -from relationships.models import * - - -class Migration: - - def forwards(self, orm): - - # Deleting unique_together for [from_user, to_user, status] on relationship. - db.delete_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id']) - - # Creating unique_together for [from_user, to_user, status, site] on Relationship. - db.create_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id', 'site_id']) - - def backwards(self, orm): - - # Deleting unique_together for [from_user, to_user, status, site] on Relationship. - db.delete_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id', 'site_id']) - - # Creating unique_together for [from_user, to_user, status] on relationship. - db.create_unique('relationships_relationship', ['from_user_id', 'to_user_id', 'status_id']) - - 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']", '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']", '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']", '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', 'site'),)"}, - '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': '24', 'related_name': "'relationships'", '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'] diff --git a/relationships/migrations/0005_add_weight_column.py b/relationships/migrations/0005_add_weight_column.py deleted file mode 100644 index a286480..0000000 --- a/relationships/migrations/0005_add_weight_column.py +++ /dev/null @@ -1,85 +0,0 @@ -# encoding: utf-8 -from south.db import db -from south.v2 import SchemaMigration - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding field 'Relationship.weight' - db.add_column('relationships_relationship', 'weight', self.gf('django.db.models.fields.FloatField')(default=1.0, null=True, blank=True), keep_default=False) - - def backwards(self, orm): - - # Deleting field 'Relationship.weight' - db.delete_column('relationships_relationship', 'weight') - - models = { - 'auth.group': { - 'Meta': {'object_name': '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'),)", 'object_name': 'Permission'}, - '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': { - 'Meta': {'object_name': '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', [], {'related_name': "'related_to'", 'symmetrical': 'False', 'through': "orm['relationships.Relationship']", 'to': "orm['auth.User']"}), - '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'),)", 'object_name': 'ContentType', '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', 'site'),)", 'object_name': 'Relationship'}, - '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', 'related_name': "'relationships'", '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']"}), - 'weight': ('django.db.models.fields.FloatField', [], {'default': '1.0', 'null': 'True', 'blank': 'True'}) - }, - 'relationships.relationshipstatus': { - 'Meta': {'object_name': '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': {'object_name': 'Site', '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'] diff --git a/relationships/models.py b/relationships/models.py index 051b6d0..0ec3a11 100644 --- a/relationships/models.py +++ b/relationships/models.py @@ -2,10 +2,10 @@ from django.conf import settings from django.contrib.sites.models import Site from django.db import models, connection -from django.db.models.fields.related import create_many_related_manager, ManyToManyRel +from django.db.models.fields.related import ManyToManyRel from django.utils.translation import ugettext_lazy as _ -from .compat import User +from relationships.compat import User class RelationshipStatusManager(models.Manager): @@ -16,6 +16,9 @@ def following(self): def blocking(self): return self.get(from_slug='blocking') + def blocked(self): + return self.get(from_slug='blocked') + def by_slug(self, status_slug): return self.get( models.Q(from_slug=status_slug) | @@ -28,15 +31,15 @@ class RelationshipStatus(models.Model): name = models.CharField(_('name'), max_length=100) verb = models.CharField(_('verb'), max_length=100) from_slug = models.CharField(_('from slug'), max_length=100, - help_text=_("Denote the relationship from the user, i.e. 'following'")) + help_text=_("Denote the relationship from the user, i.e. 'following'")) to_slug = models.CharField(_('to slug'), max_length=100, - help_text=_("Denote the relationship to the user, i.e. 'followers'")) + help_text=_("Denote the relationship to the user, i.e. 'followers'")) symmetrical_slug = models.CharField(_('symmetrical slug'), max_length=100, - help_text=_("When a mutual relationship exists, i.e. 'friends'")) + help_text=_("When a mutual relationship exists, i.e. 'friends'")) login_required = models.BooleanField(_('login required'), default=False, - help_text=_("Users must be logged in to see these relationships")) + help_text=_("Users must be logged in to see these relationships")) private = models.BooleanField(_('private'), default=False, - help_text=_("Only the user who owns these relationships can see them")) + help_text=_("Only the user who owns these relationships can see them")) objects = RelationshipStatusManager() @@ -45,20 +48,21 @@ class Meta: verbose_name = _('Relationship status') verbose_name_plural = _('Relationship statuses') - def __unicode__(self): + def __str__(self): return self.name class Relationship(models.Model): - from_user = models.ForeignKey(User, - related_name='from_users', verbose_name=_('from user')) - to_user = models.ForeignKey(User, - related_name='to_users', verbose_name=_('to user')) - status = models.ForeignKey(RelationshipStatus, verbose_name=_('status')) + from_user = models.ForeignKey(settings.AUTH_USER_MODEL, + related_name='from_users', verbose_name=_('from user'), on_delete=models.CASCADE) + to_user = models.ForeignKey(settings.AUTH_USER_MODEL, + related_name='to_users', verbose_name=_('to user'), on_delete=models.CASCADE) + status = models.ForeignKey(RelationshipStatus, verbose_name=_('status'), on_delete=models.CASCADE) created = models.DateTimeField(_('created'), auto_now_add=True) + updated_at = models.DateTimeField(_('updated_at'), auto_now=True) weight = models.FloatField(_('weight'), default=1.0, blank=True, null=True) site = models.ForeignKey(Site, default=settings.SITE_ID, - verbose_name=_('site'), related_name='relationships') + verbose_name=_('site'), related_name='relationships', on_delete=models.CASCADE) class Meta: unique_together = (('from_user', 'to_user', 'status', 'site'),) @@ -66,13 +70,19 @@ class Meta: verbose_name = _('Relationship') verbose_name_plural = _('Relationships') - def __unicode__(self): - return (_('Relationship from %(from_user)s to %(to_user)s') - % {'from_user': self.from_user.username, - 'to_user': self.to_user.username}) + def __str__(self): + return _('Relationship from {from_user} to {to_user}').format( + from_user=self.from_user.get_username(), + to_user=self.to_user.get_username() + ) + -field = models.ManyToManyField(User, through=Relationship, - symmetrical=False, related_name='related_to') +field = models.ManyToManyField( + User, + through=Relationship, + symmetrical=False, + related_name='related_to' +) class RelationshipManager(User._default_manager.__class__): @@ -109,6 +119,32 @@ def add(self, user, status=None, symmetrical=False): else: return relationship + def get_relationship_obj(self, user, status=None, symmetrical=False): + if not status: + status = RelationshipStatus.objects.following() + + relationship = Relationship.objects.get( + from_user=self.instance, + to_user=user, + status=status, + site=Site.objects.get_current() + ) + + if symmetrical: + return (relationship, user.relationships.get_relationship_obj(self.instance, status, False)) + else: + return relationship + + def filter_relationships(self, status=None): + if not status: + status = RelationshipStatus.objects.following() + + return Relationship.objects.filter( + from_user=self.instance, + status=status, + site=Site.objects.get_current() + ) + def remove(self, user, status=None, symmetrical=False): """ Remove a relationship from one user to another, with the same caveats @@ -223,65 +259,26 @@ def friends(self): return self.get_relationships(RelationshipStatus.objects.following(), True) -if django.VERSION < (1, 2): +fake_rel = ManyToManyRel( + field=None, + to=User, + through=Relationship +) - RelatedManager = create_many_related_manager(RelationshipManager, Relationship) +from relationships.compat import create_many_related_manager - class RelationshipsDescriptor(object): - def __get__(self, instance, instance_type=None): - qn = connection.ops.quote_name - manager = RelatedManager( - model=User, - core_filters={'related_to__pk': instance._get_pk_val()}, - instance=instance, - symmetrical=False, - join_table=qn('relationships_relationship'), - source_col_name=qn('from_user_id'), - target_col_name=qn('to_user_id'), - ) - return manager - -elif django.VERSION > (1, 2) and django.VERSION < (1, 4): +RelatedManager = create_many_related_manager(RelationshipManager, fake_rel) - fake_rel = ManyToManyRel( - to=User, - through=Relationship) - RelatedManager = create_many_related_manager(RelationshipManager, fake_rel) - - class RelationshipsDescriptor(object): - def __get__(self, instance, instance_type=None): - manager = RelatedManager( - model=User, - core_filters={'related_to__pk': instance._get_pk_val()}, - instance=instance, - symmetrical=False, - source_field_name='from_user', - target_field_name='to_user' - ) - return manager - -else: - - fake_rel = ManyToManyRel( - to=User, - through=Relationship) - - RelatedManager = create_many_related_manager(RelationshipManager, fake_rel) - - class RelationshipsDescriptor(object): - def __get__(self, instance, instance_type=None): - manager = RelatedManager( - model=User, - query_field_name='related_to', - instance=instance, - symmetrical=False, - source_field_name='from_user', - target_field_name='to_user', - through=Relationship, - ) - return manager - -#HACK -field.contribute_to_class(User, 'relationships') -setattr(User, 'relationships', RelationshipsDescriptor()) +class RelationshipsDescriptor(object): + def __get__(self, instance, instance_type=None): + manager = RelatedManager( + model=User, + query_field_name='related_to', + instance=instance, + symmetrical=False, + source_field_name='from_user', + target_field_name='to_user', + through=Relationship, + ) + return manager diff --git a/relationships/templates/relationships/success.html b/relationships/templates/relationships/success.html index a00d70e..dbcc498 100644 --- a/relationships/templates/relationships/success.html +++ b/relationships/templates/relationships/success.html @@ -4,5 +4,5 @@ {% block content %}
{% trans "You are " %}{% if add %}{% trans "now" %}{% else %}{% trans "no longer" %}{% endif %} {{ status.from_slug }} {{ to_user.username }}
- +{% blocktrans with username=to_user.username url=to_user.get_absolute_url %}Do you want going to {{ username }}'s profile{% endblocktrans %}
{% endblock %} diff --git a/relationships/templatetags/relationship_tags.py b/relationships/templatetags/relationship_tags.py index b660afe..6cbc39d 100644 --- a/relationships/templatetags/relationship_tags.py +++ b/relationships/templatetags/relationship_tags.py @@ -1,77 +1,65 @@ from django import template -from django.core.urlresolvers import reverse -from django.db.models.loading import get_model -from django.template import TemplateSyntaxError -from django.utils.functional import wraps -from relationships.models import RelationshipStatus -from relationships.utils import positive_filter, negative_filter - -register = template.Library() - +from django.urls import reverse -class IfRelationshipNode(template.Node): - def __init__(self, nodelist_true, nodelist_false, *args): - self.nodelist_true = nodelist_true - self.nodelist_false = nodelist_false - self.from_user, self.to_user, self.status = args - self.status = self.status.replace('"', '') # strip quotes +try: + from django.db.models.loading import get_model +except ImportError: + from django.apps import apps - def render(self, context): - from_user = template.resolve_variable(self.from_user, context) - to_user = template.resolve_variable(self.to_user, context) + get_model = apps.get_model - if from_user.is_anonymous() or to_user.is_anonymous(): - return self.nodelist_false.render(context) - - try: - status = RelationshipStatus.objects.by_slug(self.status) - except RelationshipStatus.DoesNotExist: - raise template.TemplateSyntaxError('RelationshipStatus not found') - - if status.from_slug == self.status: - val = from_user.relationships.exists(to_user, status) - elif status.to_slug == self.status: - val = to_user.relationships.exists(from_user, status) - else: - val = from_user.relationships.exists(to_user, status, symmetrical=True) +from django.template import TemplateSyntaxError +from django.utils.functional import wraps +from django.utils.translation import ugettext as _ - if val: - return self.nodelist_true.render(context) +from relationships.models import RelationshipStatus +from relationships.utils import ( + positive_filter, + negative_filter +) - return self.nodelist_false.render(context) +register = template.Library() -@register.tag -def if_relationship(parser, token): +@register.simple_tag +def relationship(from_user, to_user, status): """ Determine if a certain type of relationship exists between two users. The ``status`` parameter must be a slug matching either the from_slug, to_slug or symmetrical_slug of a RelationshipStatus. Example:: - - {% if_relationship from_user to_user "friends" %} + {% relationship from_user to_user "friends" as felas %} + {% relationship from_user to_user "blocking" as blocked %} + {% if felas %} Here are pictures of me drinking alcohol + {% elif blocked %} + damn seo experts {% else %} Sorry coworkers - {% endif_relationship %} - - {% if_relationship from_user to_user "blocking" %} - damn seo experts - {% endif_relationship %} + {% endif %} """ - bits = list(token.split_contents()) - if len(bits) != 4: - raise TemplateSyntaxError("%r takes 3 arguments:\n%s" % (bits[0], if_relationship.__doc__)) - end_tag = 'end' + bits[0] - nodelist_true = parser.parse(('else', end_tag)) - token = parser.next_token() - if token.contents == 'else': - nodelist_false = parser.parse((end_tag,)) - parser.delete_first_token() + requested_status = status.replace('"', '') # strip quotes + + if from_user.is_anonymous() or to_user.is_anonymous(): + return False + + try: + status = RelationshipStatus.objects.by_slug(requested_status) + except RelationshipStatus.DoesNotExist: + raise template.TemplateSyntaxError('RelationshipStatus not found') + + if status.from_slug == requested_status: + val = from_user.relationships.exists(to_user, status) + elif status.to_slug == requested_status: + val = to_user.relationships.exists(from_user, status) else: - nodelist_false = template.NodeList() - return IfRelationshipNode(nodelist_true, nodelist_false, *bits[1:]) + val = from_user.relationships.exists(to_user, status, symmetrical=True) + + if val: + return True + + return False @register.filter @@ -116,6 +104,7 @@ def inner(qs, user): if user.is_anonymous(): return qs.none() return func(qs, user) + inner._decorated_function = getattr(func, '_decorated_function', func) return wraps(func)(inner) @@ -130,6 +119,7 @@ def inner(qs, user): if user.is_anonymous(): return qs return func(qs, user) + inner._decorated_function = getattr(func, '_decorated_function', func) return wraps(func)(inner) diff --git a/relationships/urls.py b/relationships/urls.py index 092e6c0..9fbc227 100644 --- a/relationships/urls.py +++ b/relationships/urls.py @@ -1,9 +1,16 @@ -from .compat import patterns, url +from relationships.compat import url - -urlpatterns = patterns('relationships.views', - url(r'^$', 'relationship_redirect', name='relationship_list_base'), - url(r'^(?P