From 737b55c960dc72d1f45a848f974065287076c0ca Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Mon, 25 May 2015 10:03:50 +0200 Subject: [PATCH 01/12] Start Megafon feature --- megafon/__init__.py | 0 megafon/admin.py | 5 ++ megafon/api.py | 56 ++++++++++++++++++ megafon/migrations/0001_initial.py | 93 ++++++++++++++++++++++++++++++ megafon/migrations/__init__.py | 0 megafon/models.py | 29 ++++++++++ requirements.txt | 1 + 7 files changed, 184 insertions(+) create mode 100644 megafon/__init__.py create mode 100644 megafon/admin.py create mode 100644 megafon/api.py create mode 100644 megafon/migrations/0001_initial.py create mode 100644 megafon/migrations/__init__.py create mode 100644 megafon/models.py diff --git a/megafon/__init__.py b/megafon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/megafon/admin.py b/megafon/admin.py new file mode 100644 index 0000000..61f4330 --- /dev/null +++ b/megafon/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from mptt.admin import MPTTModelAdmin +from .models import Post + +admin.site.register(Post, MPTTModelAdmin) diff --git a/megafon/api.py b/megafon/api.py new file mode 100644 index 0000000..b99dee3 --- /dev/null +++ b/megafon/api.py @@ -0,0 +1,56 @@ +from django.conf.urls import * + +from tastypie.resources import ModelResource +from tastypie import fields + +from .models import Post + +from graffiti.api import TaggedItemResource +from dataserver.authentication import AnonymousApiKeyAuthentication +from tastypie.authorization import DjangoAuthorization +from tastypie.constants import ALL_WITH_RELATIONS +from tastypie.utils import trailing_slash + +from accounts.api import ProfileResource + + +class PostResource(ModelResource): + + """ A post resource """ + + author = fields.OneToOneField(ProfileResource, 'author', full=True) + tags = fields.ToManyField(TaggedItemResource, 'tagged_items', full=True, null=True) + + class Meta: + queryset = Post.objects.all() + resource_name = 'megafon/post' + allowed_methods = ['get'] + + filtering = { + "slug": ('exact',), + "level" : ('exact', ), + } + + def prepend_urls(self): + return [ + url(r"^(?P%s)/questions%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_questions'), name="api_get_questions"), + url(r"^(?P%s)/(?P\w[\w/-]*)/answers%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_answers'), name="api_get_answers"), + ] + + def dehydrate(self, bundle): + bundle.data['answers_count'] = bundle.obj.get_descendant_count() + return bundle + + def get_questions(self, request, **kwargs): + kwargs['level'] = 0 + return self.get_list(request, **kwargs) + + + def get_answers(self, request, **kwargs): + self.method_check(request, allowed=['get']) + self.is_authenticated(request) + self.throttle_check(request) + + question = self.get_object_list(request).get(id=kwargs['pk']) + bundles = [self.full_dehydrate(self.build_bundle(obj=answer, request=request)) for answer in question.get_children()] + return self.create_response(request, bundles) diff --git a/megafon/migrations/0001_initial.py b/megafon/migrations/0001_initial.py new file mode 100644 index 0000000..c244a65 --- /dev/null +++ b/megafon/migrations/0001_initial.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Post' + db.create_table(u'megafon_post', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length='200', blank=True)), + ('posted_on', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['accounts.Profile'])), + ('text', self.gf('django.db.models.fields.TextField')()), + ('slug', self.gf('autoslug.fields.AutoSlugField')(unique_with=('id',), max_length=50, populate_from=None)), + ('parent', self.gf('mptt.fields.TreeForeignKey')(blank=True, related_name='answers', null=True, to=orm['megafon.Post'])), + (u'lft', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), + (u'rght', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), + (u'tree_id', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), + (u'level', self.gf('django.db.models.fields.PositiveIntegerField')(db_index=True)), + )) + db.send_create_signal(u'megafon', ['Post']) + + + def backwards(self, orm): + # Deleting model 'Post' + db.delete_table(u'megafon_post') + + + models = { + u'accounts.profile': { + 'Meta': {'object_name': 'Profile'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mugshot': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'privacy': ('django.db.models.fields.CharField', [], {'default': "'registered'", 'max_length': '15'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'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'}) + }, + u'megafon.post': { + 'Meta': {'object_name': 'Post'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.Profile']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'answers'", 'null': 'True', 'to': u"orm['megafon.Post']"}), + 'posted_on': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': "('id',)", 'max_length': '50', 'populate_from': 'None'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': "'200'", 'blank': 'True'}), + u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) + } + } + + complete_apps = ['megafon'] \ No newline at end of file diff --git a/megafon/migrations/__init__.py b/megafon/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/megafon/models.py b/megafon/models.py new file mode 100644 index 0000000..a5cbe61 --- /dev/null +++ b/megafon/models.py @@ -0,0 +1,29 @@ +from django.db import models +from autoslug.fields import AutoSlugField +from taggit.managers import TaggableManager +from accounts.models import Profile +from mptt.models import MPTTModel, TreeForeignKey + +import random, string + +class Post(MPTTModel): + + """ A post """ + + def populate_slug(instance): + if instance.title: + return instance.title + else: + return "answer-%s" % ''.join(random.choice(string.lowercase) for i in range(20)) + + title = models.CharField(max_length="200", blank=True, null=False) + posted_on = models.DateField(auto_now_add=True) + author = models.ForeignKey(Profile) + text = models.TextField() + slug = AutoSlugField(populate_from=populate_slug, unique_with='id') + tags = TaggableManager(blank=True) + + parent = TreeForeignKey('self', null=True, blank=True, related_name='answers', db_index=True) + + class MPTTMeta: + order_insertion_by = ['posted_on'] diff --git a/requirements.txt b/requirements.txt index 0f7254e..91c313b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ django-guardian==1.2.5 django-simple-history==1.5.4 django-tastypie==0.12.1 django-userena==1.4.0 +django-mptt==0.7.3 psycopg2==2.5.4 python-dateutil==2.1 From ec3c8f4ed1eb070cadcfad839072e187f73d19bd Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Thu, 28 May 2015 10:06:07 +0200 Subject: [PATCH 02/12] Fix custom end-point method --- megafon/api.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/megafon/api.py b/megafon/api.py index b99dee3..0348c3a 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -4,6 +4,7 @@ from tastypie import fields from .models import Post +from accounts.models import Profile from graffiti.api import TaggedItemResource from dataserver.authentication import AnonymousApiKeyAuthentication @@ -35,6 +36,7 @@ def prepend_urls(self): return [ url(r"^(?P%s)/questions%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_questions'), name="api_get_questions"), url(r"^(?P%s)/(?P\w[\w/-]*)/answers%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_answers'), name="api_get_answers"), + url(r"^(?P%s)/(?P\w[\w/-]*)/contributors%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_contributors'), name="api_get_contributors"), ] def dehydrate(self, bundle): @@ -51,6 +53,28 @@ def get_answers(self, request, **kwargs): self.is_authenticated(request) self.throttle_check(request) + post = self.get_object_list(request).get(id=kwargs['pk']) + answers = post.get_children() + bundles = [] + + for obj in answers: + bundle = self.build_bundle(obj=obj, request=request) + bundles.append(self.full_dehydrate(bundle, for_list=True)) + + return self.create_response(request, {'objects' : bundles}) + + def get_contributors(self, request, **kwargs): + self.method_check(request, allowed=['get']) + self.is_authenticated(request) + self.throttle_check(request) + question = self.get_object_list(request).get(id=kwargs['pk']) - bundles = [self.full_dehydrate(self.build_bundle(obj=answer, request=request)) for answer in question.get_children()] - return self.create_response(request, bundles) + contributors = question.get_descendants(include_self=False).values_list('author', flat=True) + bundles = [] + + for obj in Profile.objects.filter(id__in=contributors): + contributor_resource = ProfileResource() + bundle = contributor_resource.build_bundle(obj=obj, request=request) + bundles.append(contributor_resource.full_dehydrate(bundle, for_list=True)) + + return self.create_response(request, {'objects' : bundles}) From 764612bc5f624a4f6733d103210e7c702de7791f Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Thu, 28 May 2015 18:00:11 +0200 Subject: [PATCH 03/12] Add post method and permissions --- megafon/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/megafon/api.py b/megafon/api.py index 0348c3a..c035258 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -25,7 +25,10 @@ class PostResource(ModelResource): class Meta: queryset = Post.objects.all() resource_name = 'megafon/post' - allowed_methods = ['get'] + allowed_methods = ['get', 'post'] + always_return_data = True + authentication = AnonymousApiKeyAuthentication() + authorization = DjangoAuthorization() filtering = { "slug": ('exact',), From 8f93533f6dd51ac65b0ca0b27f6a18450e07f132 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Thu, 28 May 2015 18:00:58 +0200 Subject: [PATCH 04/12] Add fields in post model --- megafon/api.py | 9 +- ...__add_field_post_answers_count__chg_fie.py | 98 +++++++++++++++++++ megafon/models.py | 14 ++- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 megafon/migrations/0002_auto__add_field_post_updated_on__add_field_post_answers_count__chg_fie.py diff --git a/megafon/api.py b/megafon/api.py index c035258..bb4aafc 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -33,7 +33,9 @@ class Meta: filtering = { "slug": ('exact',), "level" : ('exact', ), + "answers_count" : ('exact', ), } + ordering = ['updated_on', 'answers_count'] def prepend_urls(self): return [ @@ -42,10 +44,6 @@ def prepend_urls(self): url(r"^(?P%s)/(?P\w[\w/-]*)/contributors%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_contributors'), name="api_get_contributors"), ] - def dehydrate(self, bundle): - bundle.data['answers_count'] = bundle.obj.get_descendant_count() - return bundle - def get_questions(self, request, **kwargs): kwargs['level'] = 0 return self.get_list(request, **kwargs) @@ -81,3 +79,6 @@ def get_contributors(self, request, **kwargs): bundles.append(contributor_resource.full_dehydrate(bundle, for_list=True)) return self.create_response(request, {'objects' : bundles}) + + # def get_best_contributors(self, request, **kwargs): + # Profile.objects.filter(id__gt=1).annotate(num_post=Count('post')).order_by('-num_post') diff --git a/megafon/migrations/0002_auto__add_field_post_updated_on__add_field_post_answers_count__chg_fie.py b/megafon/migrations/0002_auto__add_field_post_updated_on__add_field_post_answers_count__chg_fie.py new file mode 100644 index 0000000..94755e5 --- /dev/null +++ b/megafon/migrations/0002_auto__add_field_post_updated_on__add_field_post_answers_count__chg_fie.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Post.updated_on' + db.add_column(u'megafon_post', 'updated_on', + self.gf('django.db.models.fields.DateTimeField')(auto_now=True, auto_now_add=True, default=datetime.datetime(2015, 5, 28, 0, 0), blank=True), + keep_default=False) + + # Adding field 'Post.answers_count' + db.add_column(u'megafon_post', 'answers_count', + self.gf('django.db.models.fields.PositiveIntegerField')(default=0), + keep_default=False) + + + # Changing field 'Post.posted_on' + db.alter_column(u'megafon_post', 'posted_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) + + def backwards(self, orm): + # Deleting field 'Post.updated_on' + db.delete_column(u'megafon_post', 'updated_on') + + # Deleting field 'Post.answers_count' + db.delete_column(u'megafon_post', 'answers_count') + + + # Changing field 'Post.posted_on' + db.alter_column(u'megafon_post', 'posted_on', self.gf('django.db.models.fields.DateField')(auto_now_add=True)) + + models = { + u'accounts.profile': { + 'Meta': {'object_name': 'Profile'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mugshot': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'privacy': ('django.db.models.fields.CharField', [], {'default': "'registered'", 'max_length': '15'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'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'}) + }, + u'megafon.post': { + 'Meta': {'object_name': 'Post'}, + 'answers_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.Profile']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'answers'", 'null': 'True', 'to': u"orm['megafon.Post']"}), + 'posted_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': "('id',)", 'max_length': '50', 'populate_from': 'None'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': "'200'", 'blank': 'True'}), + u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'updated_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['megafon'] \ No newline at end of file diff --git a/megafon/models.py b/megafon/models.py index a5cbe61..2575e31 100644 --- a/megafon/models.py +++ b/megafon/models.py @@ -3,6 +3,7 @@ from taggit.managers import TaggableManager from accounts.models import Profile from mptt.models import MPTTModel, TreeForeignKey +from django.db.models.signals import post_save import random, string @@ -17,13 +18,24 @@ def populate_slug(instance): return "answer-%s" % ''.join(random.choice(string.lowercase) for i in range(20)) title = models.CharField(max_length="200", blank=True, null=False) - posted_on = models.DateField(auto_now_add=True) + posted_on = models.DateTimeField(auto_now_add=True) + updated_on = models.DateTimeField(auto_now_add=True, auto_now=True) author = models.ForeignKey(Profile) text = models.TextField() slug = AutoSlugField(populate_from=populate_slug, unique_with='id') tags = TaggableManager(blank=True) parent = TreeForeignKey('self', null=True, blank=True, related_name='answers', db_index=True) + answers_count = models.PositiveIntegerField(default=0) class MPTTMeta: order_insertion_by = ['posted_on'] + + +def update_answers_count(sender, instance, created, **kwargs): + if not instance.is_root_node(): + instance.parent.answers_count = instance.parent.get_descendant_count() + instance.parent.save() + +# register the signal +post_save.connect(update_answers_count, sender=Post, dispatch_uid="update_answers_count") From 5356be3dd8e0f6781a4c677c66d68845480fcfae Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Tue, 2 Jun 2015 17:10:39 +0200 Subject: [PATCH 05/12] Add parent to resource --- megafon/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/megafon/api.py b/megafon/api.py index bb4aafc..7d7e314 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -21,6 +21,8 @@ class PostResource(ModelResource): author = fields.OneToOneField(ProfileResource, 'author', full=True) tags = fields.ToManyField(TaggedItemResource, 'tagged_items', full=True, null=True) + parent = fields.OneToOneField("megafon.api.PostResource", 'parent', null=True) + class Meta: queryset = Post.objects.all() @@ -55,7 +57,7 @@ def get_answers(self, request, **kwargs): self.throttle_check(request) post = self.get_object_list(request).get(id=kwargs['pk']) - answers = post.get_children() + answers = post.get_children().order_by('-updated_on') bundles = [] for obj in answers: From 5c90317bc6e8922864378d0b1982e9b18c659fd0 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Fri, 12 Jun 2015 23:56:14 +0200 Subject: [PATCH 06/12] Remove author field Now manage with ObjectProfileLink --- megafon/api.py | 1 - .../0003_auto__del_field_post_author.py | 44 +++++++++++++++++++ megafon/models.py | 6 ++- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 megafon/migrations/0003_auto__del_field_post_author.py diff --git a/megafon/api.py b/megafon/api.py index 7d7e314..1962d23 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -19,7 +19,6 @@ class PostResource(ModelResource): """ A post resource """ - author = fields.OneToOneField(ProfileResource, 'author', full=True) tags = fields.ToManyField(TaggedItemResource, 'tagged_items', full=True, null=True) parent = fields.OneToOneField("megafon.api.PostResource", 'parent', null=True) diff --git a/megafon/migrations/0003_auto__del_field_post_author.py b/megafon/migrations/0003_auto__del_field_post_author.py new file mode 100644 index 0000000..931f418 --- /dev/null +++ b/megafon/migrations/0003_auto__del_field_post_author.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Post.author' + db.delete_column(u'megafon_post', 'author_id') + + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Post.author' + raise RuntimeError("Cannot reverse this migration. 'Post.author' and its values cannot be restored.") + + # The following code is provided here to aid in writing a correct migration # Adding field 'Post.author' + db.add_column(u'megafon_post', 'author', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['accounts.Profile']), + keep_default=False) + + + models = { + u'megafon.post': { + 'Meta': {'object_name': 'Post'}, + 'answers_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'parent': ('mptt.fields.TreeForeignKey', [], {'blank': 'True', 'related_name': "'answers'", 'null': 'True', 'to': u"orm['megafon.Post']"}), + 'posted_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': "('id',)", 'max_length': '50', 'populate_from': 'None'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': "'200'", 'blank': 'True'}), + u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'updated_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['megafon'] \ No newline at end of file diff --git a/megafon/models.py b/megafon/models.py index 2575e31..120f820 100644 --- a/megafon/models.py +++ b/megafon/models.py @@ -9,7 +9,10 @@ class Post(MPTTModel): - """ A post """ + """ + A post + Author is defined with accounts.ObjectProfileLink + """ def populate_slug(instance): if instance.title: @@ -20,7 +23,6 @@ def populate_slug(instance): title = models.CharField(max_length="200", blank=True, null=False) posted_on = models.DateTimeField(auto_now_add=True) updated_on = models.DateTimeField(auto_now_add=True, auto_now=True) - author = models.ForeignKey(Profile) text = models.TextField() slug = AutoSlugField(populate_from=populate_slug, unique_with='id') tags = TaggableManager(blank=True) From 5884a35db70698504a0c5f6ecf8e12fc1d81f664 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Fri, 12 Jun 2015 23:56:52 +0200 Subject: [PATCH 07/12] Implements best linked profiles method --- accounts/api.py | 29 +++++++++++++++++++++++++++++ megafon/api.py | 22 +--------------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/accounts/api.py b/accounts/api.py index c9e9fd5..2608b9a 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -1,6 +1,7 @@ # -*- encoding: utf-8 -*- import json +from django.db.models import Count from django.conf.urls import url from django.contrib.auth.models import User, Group from django.contrib.auth import authenticate, logout @@ -211,6 +212,7 @@ class Meta: filtering = { "object_id" : ['exact', ], "content_type" : ['exact', ], + "level" : ['exact', ], "profile" : ALL_WITH_RELATIONS, } @@ -221,6 +223,9 @@ def prepend_urls(self): url(r"^(?P%s)/(?P\w+?)/(?P\d+?)%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('dispatch_list'), name="api_dispatch_list"), + url(r"^(?P%s)/(?P\w+?)/best%s$" % (self._meta.resource_name, trailing_slash()), + self.wrap_view('get_best_linked_profiles'), + name="api_best_linked_profiles"), ] def dispatch_list(self, request, **kwargs): @@ -250,3 +255,27 @@ def dispatch_list(self, request, **kwargs): location=self.get_resource_uri(bundle)) return ModelResource.dispatch_list(self, request, **kwargs) + + def get_best_linked_profiles(self, request, **kwargs): + self.method_check(request, allowed=['get']) + self.is_authenticated(request) + self.throttle_check(request) + + if 'content_type' in kwargs: + levels = [] + if 'level' in request.GET: + levels = [int(lvl) for lvl in request.GET.getlist('level')] + + profiles = Profile.objects.filter(id__gt=1, + objectprofilelink__content_type__model=kwargs["content_type"], + objectprofilelink__level__in=levels).annotate(num_post=Count('objectprofilelink')).order_by('-num_post') + + bundles = [] + for obj in profiles: + profile_resource = ProfileResource() + bundle = profile_resource.build_bundle(obj=obj, request=request) + bundles.append(profile_resource.full_dehydrate(bundle, for_list=True)) + + return self.create_response(request, {'objects' : bundles}) + + return ModelResource.dispatch_list(self, request, **kwargs) diff --git a/megafon/api.py b/megafon/api.py index 1962d23..41e5915 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -42,7 +42,6 @@ def prepend_urls(self): return [ url(r"^(?P%s)/questions%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_questions'), name="api_get_questions"), url(r"^(?P%s)/(?P\w[\w/-]*)/answers%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_answers'), name="api_get_answers"), - url(r"^(?P%s)/(?P\w[\w/-]*)/contributors%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_contributors'), name="api_get_contributors"), ] def get_questions(self, request, **kwargs): @@ -57,29 +56,10 @@ def get_answers(self, request, **kwargs): post = self.get_object_list(request).get(id=kwargs['pk']) answers = post.get_children().order_by('-updated_on') - bundles = [] + bundles = [] for obj in answers: bundle = self.build_bundle(obj=obj, request=request) bundles.append(self.full_dehydrate(bundle, for_list=True)) return self.create_response(request, {'objects' : bundles}) - - def get_contributors(self, request, **kwargs): - self.method_check(request, allowed=['get']) - self.is_authenticated(request) - self.throttle_check(request) - - question = self.get_object_list(request).get(id=kwargs['pk']) - contributors = question.get_descendants(include_self=False).values_list('author', flat=True) - bundles = [] - - for obj in Profile.objects.filter(id__in=contributors): - contributor_resource = ProfileResource() - bundle = contributor_resource.build_bundle(obj=obj, request=request) - bundles.append(contributor_resource.full_dehydrate(bundle, for_list=True)) - - return self.create_response(request, {'objects' : bundles}) - - # def get_best_contributors(self, request, **kwargs): - # Profile.objects.filter(id__gt=1).annotate(num_post=Count('post')).order_by('-num_post') From 6d25b49d68631a4e2cbe78e9800eb203582b3911 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Mon, 22 Jun 2015 19:46:12 +0200 Subject: [PATCH 08/12] Add id filtering on Post --- megafon/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/megafon/api.py b/megafon/api.py index 41e5915..9c760aa 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -33,6 +33,7 @@ class Meta: filtering = { "slug": ('exact',), + "id": ('exact',), "level" : ('exact', ), "answers_count" : ('exact', ), } From 99710322889ef913513464a880cbeff784029188 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Mon, 22 Jun 2015 20:36:36 +0200 Subject: [PATCH 09/12] Add end-point to fetch the root post --- megafon/api.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/megafon/api.py b/megafon/api.py index 9c760aa..cb60f7f 100644 --- a/megafon/api.py +++ b/megafon/api.py @@ -43,6 +43,7 @@ def prepend_urls(self): return [ url(r"^(?P%s)/questions%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_questions'), name="api_get_questions"), url(r"^(?P%s)/(?P\w[\w/-]*)/answers%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_answers'), name="api_get_answers"), + url(r"^(?P%s)/(?P\w[\w/-]*)/root%s$" % (self._meta.resource_name, trailing_slash()), self.wrap_view('get_root'), name="api_get_root"), ] def get_questions(self, request, **kwargs): @@ -64,3 +65,17 @@ def get_answers(self, request, **kwargs): bundles.append(self.full_dehydrate(bundle, for_list=True)) return self.create_response(request, {'objects' : bundles}) + + def get_root(self, request, **kwargs): + self.method_check(request, allowed=['get']) + self.is_authenticated(request) + self.throttle_check(request) + + try : + post = self.get_object_list(request).get(id=kwargs['pk']).get_root() + bundle = self.build_bundle(obj=post, request=request) + return self.create_response(request, self.full_dehydrate(bundle)) + except: + pass + + return self.create_response(request, {}) From e4ac94472e5844e5fd7e71b490556314185ea422 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Wed, 1 Jul 2015 17:15:40 +0200 Subject: [PATCH 10/12] ProjectProgress icon can be nullable --- ...15_auto__chg_field_projectprogress_icon.py | 139 ++++++++++++++++++ projects/models.py | 2 +- 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 projects/migrations/0015_auto__chg_field_projectprogress_icon.py diff --git a/projects/migrations/0015_auto__chg_field_projectprogress_icon.py b/projects/migrations/0015_auto__chg_field_projectprogress_icon.py new file mode 100644 index 0000000..724963a --- /dev/null +++ b/projects/migrations/0015_auto__chg_field_projectprogress_icon.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'ProjectProgress.icon' + db.alter_column(u'projects_projectprogress', 'icon', self.gf('django.db.models.fields.files.ImageField')(max_length=100, null=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'ProjectProgress.icon' + raise RuntimeError("Cannot reverse this migration. 'ProjectProgress.icon' and its values cannot be restored.") + + # The following code is provided here to aid in writing a correct migration + # Changing field 'ProjectProgress.icon' + db.alter_column(u'projects_projectprogress', 'icon', self.gf('django.db.models.fields.files.ImageField')(max_length=100)) + + models = { + u'accounts.profile': { + 'Meta': {'object_name': 'Profile'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mugshot': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), + 'privacy': ('django.db.models.fields.CharField', [], {'default': "'registered'", 'max_length': '15'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'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'}) + }, + u'projects.historicalproject': { + 'Meta': {'ordering': "(u'-history_date', u'-history_id')", 'object_name': 'HistoricalProject'}, + 'baseline': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'begin_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + u'history_date': ('django.db.models.fields.DateTimeField', [], {}), + u'history_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + u'history_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + u'history_user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + u'id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'blank': 'True'}), + 'location_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'progress_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique_with': '()', 'max_length': '50', 'populate_from': 'None'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + u'projects.project': { + 'Meta': {'object_name': 'Project'}, + 'baseline': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'begin_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'location': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['scout.Place']", 'null': 'True', 'blank': 'True'}), + 'progress': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['projects.ProjectProgress']", 'null': 'True', 'blank': 'True'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique': 'True', 'max_length': '50', 'populate_from': 'None', 'unique_with': '()'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}) + }, + u'projects.projectprogress': { + 'Meta': {'ordering': "['order']", 'object_name': 'ProjectProgress'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '500'}), + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'progress_range': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['projects.ProjectProgressRange']"}) + }, + u'projects.projectprogressrange': { + 'Meta': {'object_name': 'ProjectProgressRange'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'slug': ('autoslug.fields.AutoSlugField', [], {'unique': 'True', 'max_length': '50', 'populate_from': "'name'", 'unique_with': '()'}) + }, + u'projects.projectteam': { + 'Meta': {'object_name': 'ProjectTeam'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['accounts.Profile']", 'symmetrical': 'False'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['projects.Project']"}) + }, + u'scout.place': { + 'Meta': {'object_name': 'Place'}, + 'address': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'place'", 'null': 'True', 'to': u"orm['scout.PostalAddress']"}), + 'geo': ('django.contrib.gis.db.models.fields.PointField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'scout.postaladdress': { + 'Meta': {'object_name': 'PostalAddress'}, + 'address_locality': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'address_region': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '2'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post_office_box_number': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'street_address': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + } + } + + complete_apps = ['projects'] \ No newline at end of file diff --git a/projects/models.py b/projects/models.py index 9d3b654..12e2554 100644 --- a/projects/models.py +++ b/projects/models.py @@ -31,7 +31,7 @@ class ProjectProgress(models.Model): order = models.PositiveIntegerField(default=0) label = models.CharField(max_length=30) description = models.CharField(max_length=500) - icon = models.ImageField(upload_to='progress_icons') + icon = models.ImageField(upload_to='progress_icons', null=True, blank=True) class Meta: ordering = ['order', ] From 9c128ea8f1071ca6dc18f259d42968672a6a184e Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Thu, 2 Jul 2015 00:00:42 +0200 Subject: [PATCH 11/12] Add date_joined to UserResource --- accounts/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/api.py b/accounts/api.py index 2608b9a..038425d 100644 --- a/accounts/api.py +++ b/accounts/api.py @@ -31,7 +31,7 @@ class Meta: resource_name = 'account/user' authentication = Authentication() authorization = Authorization() - fields = ['id', 'username', 'first_name', 'last_name', 'groups', 'email'] + fields = ['id', 'username', 'first_name', 'last_name', 'groups', 'email', 'date_joined'] filtering = { "id" : ['exact',], "username": ALL_WITH_RELATIONS, From 0af155b56fd3f68150088d2990a8a05affe9cab7 Mon Sep 17 00:00:00 2001 From: Alban Tiberghien Date: Thu, 2 Jul 2015 00:00:52 +0200 Subject: [PATCH 12/12] Do not create profile for admin --- accounts/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/models.py b/accounts/models.py index d6f43bc..7d98513 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -41,7 +41,7 @@ class ObjectProfileLink(models.Model): @receiver(post_save, sender=User) def create_profile_on_user_signup(sender, created, instance, **kwargs): - if created: + if created and not instance.is_superuser: profile_model = get_profile_model() profile_model.objects.create(user=instance)