diff --git a/settings/local.py.example b/settings/local.py.example index 0c3c4415..9ba66b76 100644 --- a/settings/local.py.example +++ b/settings/local.py.example @@ -46,3 +46,10 @@ from .development import * #EMAIL_HOST_USER = 'youremail@gmail.com' #EMAIL_HOST_PASSWORD = 'yourpassword' #EMAIL_PORT = 587 + +# GITHUP SETTINGS +#GITHUB_URL = "https://github.com/" +#GITHUB_API_URL = "https://api.github.com/" +#GITHUB_API_CLIENT_ID = "yourgithubclientid" +#GITHUB_API_CLIENT_SECRET = "yourgithubclientsecret" + diff --git a/taiga/auth/serializers.py b/taiga/auth/serializers.py index f9b4e367..2b235d1d 100644 --- a/taiga/auth/serializers.py +++ b/taiga/auth/serializers.py @@ -17,8 +17,7 @@ from rest_framework import serializers class BaseRegisterSerializer(serializers.Serializer): - first_name = serializers.CharField(max_length=200) - last_name = serializers.CharField(max_length=200) + full_name = serializers.CharField(max_length=256) email = serializers.EmailField(max_length=200) username = serializers.CharField(max_length=200) password = serializers.CharField(min_length=4) diff --git a/taiga/auth/tests.py b/taiga/auth/tests.py index 1c023e3e..face1b4f 100644 --- a/taiga/auth/tests.py +++ b/taiga/auth/tests.py @@ -105,15 +105,13 @@ class AuthServicesTests(test.TestCase): username=self.user1.username, password="secret", email=self.user1.email, - first_name="foo", - last_name="bar") + full_name="foo") user = services.public_register(self.domain, username="foousername", password="foosecret", email="foo@bar.ca", - first_name="Foo", - last_name="Bar") + full_name="Foo") self.assertEqual(user.username, "foousername") self.assertTrue(user.check_password("foosecret")) self.assertTrue(is_user_exists_on_domain(self.domain, user)) @@ -153,8 +151,7 @@ class AuthServicesTests(test.TestCase): username="user2", password="user2", email="user2@bar.ca", - first_name="Foo", - last_name="Bar") + full_name="Foo") membership = membership.__class__.objects.get(pk=membership.pk) @@ -195,8 +192,7 @@ class RegisterApiTests(test.TestCase): data = { "username": "pepe", "password": "pepepepe", - "first_name": "pepe", - "last_name": "pepe", + "full_name": "pepe", "email": "pepe@pepe.com", "type": "public", } @@ -213,8 +209,7 @@ class RegisterApiTests(test.TestCase): data = { "username": "pepe", "password": "pepepepe", - "first_name": "pepe", - "last_name": "pepe", + "full_name": "pepe", "email": "pepe@pepe.com", "type": "public", } @@ -227,8 +222,7 @@ class RegisterApiTests(test.TestCase): data = { "username": "pepe", "password": "pepepepe", - "first_name": "pepe", - "last_name": "pepe", + "full_name": "pepe", "email": "pepe@pepe.com", "type": "private", } @@ -243,8 +237,7 @@ class RegisterApiTests(test.TestCase): data = { "username": "pepe", "password": "pepepepe", - "first_name": "pepe", - "last_name": "pepe", + "full_name": "pepe", "email": "pepe@pepe.com", "type": "private", "existing": False, diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index 347b97dd..1e54602d 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -96,8 +96,7 @@ class IssuesOrdering(filters.FilterBackend): if order_by in ['owner', '-owner', 'assigned_to', '-assigned_to']: return queryset.order_by( - '{}__first_name'.format(order_by), - '{}__last_name'.format(order_by) + '{}__full_name'.format(order_by) ) return queryset diff --git a/taiga/projects/management/commands/sample_data.py b/taiga/projects/management/commands/sample_data.py index 838905eb..bb9d3a7b 100644 --- a/taiga/projects/management/commands/sample_data.py +++ b/taiga/projects/management/commands/sample_data.py @@ -290,8 +290,7 @@ class Command(BaseCommand): def create_user(self, counter): user = User.objects.create( username='user-{0}'.format(counter), - first_name=self.sd.name('es'), - last_name=self.sd.surname('es', number=1), + full_name="{} {}".format(self.sd.name('es'), self.sd.surname('es', number=1)), email=self.sd.email(), token=''.join(random.sample('abcdef0123456789', 10)), color=self.sd.choice(COLOR_CHOICES)) diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index aeb2f868..b231e07e 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -109,7 +109,7 @@ class ProjectDetailSerializer(ProjectSerializer): issue_types = IssueTypeSerializer(many=True, required=False) def get_membership(self, obj): - qs = obj.memberships.order_by('user__first_name', 'user__last_name', 'user__username') + qs = obj.memberships.order_by('user__full_name', 'user__username') qs = qs.select_related("role", "user") serializer = ProjectMembershipSerializer(qs, many=True) @@ -117,7 +117,7 @@ class ProjectDetailSerializer(ProjectSerializer): def get_active_membership(self, obj): qs = obj.memberships.filter(user__isnull=False) - qs = qs.order_by('user__first_name', 'user__last_name', 'user__username') + qs = qs.order_by('user__full_name', 'user__username') qs = qs.select_related("role", "user") serializer = ProjectMembershipSerializer(qs, many=True) diff --git a/taiga/projects/tests/tests_api.py b/taiga/projects/tests/tests_api.py index 32a772ba..f2deffff 100644 --- a/taiga/projects/tests/tests_api.py +++ b/taiga/projects/tests/tests_api.py @@ -73,7 +73,7 @@ class ProfileTestCase(test.TestCase): password=self.user3.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.patch( reverse("users-detail", args=[self.user2.pk]), @@ -86,7 +86,7 @@ class ProfileTestCase(test.TestCase): password=self.user3.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.patch( reverse("users-detail", args=[self.user3.pk]), content_type="application/json", @@ -99,7 +99,7 @@ class ProfileTestCase(test.TestCase): password=self.user1.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.patch( reverse("users-detail", args=[self.user3.pk]), content_type="application/json", @@ -112,7 +112,7 @@ class ProfileTestCase(test.TestCase): password=self.user3.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.delete( reverse("users-detail", args=[self.user2.pk])) self.assertEqual(response.status_code, 404) @@ -122,7 +122,7 @@ class ProfileTestCase(test.TestCase): password=self.user3.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.delete( reverse("users-detail", args=[self.user3.pk])) @@ -134,7 +134,7 @@ class ProfileTestCase(test.TestCase): password=self.user1.username) self.assertTrue(response) - data = {"first_name": "Foo Bar"} + data = {"full_name": "Foo Bar"} response = self.client.delete( reverse("users-detail", args=[self.user3.pk])) diff --git a/taiga/projects/votes/serializers.py b/taiga/projects/votes/serializers.py index 2bd473f2..1c089aff 100644 --- a/taiga/projects/votes/serializers.py +++ b/taiga/projects/votes/serializers.py @@ -8,4 +8,4 @@ class VoterSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ('id', 'username', 'first_name', 'last_name', 'full_name') + fields = ('id', 'username', 'full_name') diff --git a/taiga/routers.py b/taiga/routers.py index f09b1d55..809544f2 100644 --- a/taiga/routers.py +++ b/taiga/routers.py @@ -20,11 +20,9 @@ router = routers.DefaultRouter(trailing_slash=False) # taiga.users from taiga.users.api import UsersViewSet -from taiga.users.api import PermissionsViewSet from taiga.auth.api import AuthViewSet router.register(r"users", UsersViewSet, base_name="users") -router.register(r"permissions", PermissionsViewSet, base_name="permissions") router.register(r"auth", AuthViewSet, base_name="auth") diff --git a/taiga/users/admin.py b/taiga/users/admin.py index 8339c635..c258e17b 100644 --- a/taiga/users/admin.py +++ b/taiga/users/admin.py @@ -47,20 +47,19 @@ admin.site.register(Role, RoleAdmin) class UserAdmin(DjangoUserAdmin): fieldsets = ( (None, {'fields': ('username', 'password')}), - (_('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'description', 'photo')}), + (_('Personal info'), {'fields': ('full_name', 'email', 'bio', 'photo')}), (_('Extra info'), {'fields': ('color', 'default_language', 'default_timezone', 'token', 'colorize_tags')}), (_('Notifications info'), {'fields': ("notify_level", "notify_changes_by_me",)}), - (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',)}), + (_('Permissions'), {'fields': ('is_active', 'is_superuser',)}), (_('Important dates'), {'fields': ('last_login', 'date_joined')}), ) form = UserChangeForm add_form = UserCreationForm - - -class PermissionAdmin(admin.ModelAdmin): - list_display = ['name', 'content_type', 'codename'] - list_filter = ['content_type'] - + list_display = ('username', 'email', 'full_name') + list_filter = ('is_superuser', 'is_active') + search_fields = ('username', 'full_name', 'email') + ordering = ('username',) + filter_horizontal = () class RoleInline(admin.TabularInline): model = Role @@ -68,4 +67,3 @@ class RoleInline(admin.TabularInline): admin.site.register(User, UserAdmin) -admin.site.register(Permission, PermissionAdmin) diff --git a/taiga/users/api.py b/taiga/users/api.py index ee280d65..0a2ef5d0 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -20,7 +20,6 @@ from django.db.models.loading import get_model from django.db.models import Q from django.shortcuts import get_object_or_404 from django.contrib.auth import logout, login, authenticate -from django.contrib.auth.models import Permission from django.utils.translation import ugettext_lazy as _ from rest_framework.response import Response @@ -35,7 +34,8 @@ from taiga.base import exceptions as exc from taiga.base.api import ModelCrudViewSet, RetrieveModelMixin, ModelListViewSet from .models import User, Role -from .serializers import UserSerializer, RecoverySerializer, PermissionSerializer +from .serializers import UserSerializer, RecoverySerializer + class MembersFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): @@ -53,27 +53,6 @@ class MembersFilterBackend(BaseFilterBackend): else: return queryset.filter(pk=request.user.id) -class PermissionsViewSet(ModelListViewSet): - permission_classes = (IsAuthenticated,) - serializer_class = PermissionSerializer - paginate_by = 0 - excluded_codenames = [ - "add_logentry", "change_logentry", "delete_logentry", - "add_group", "change_group", "delete_group", - "add_permission", "change_permission", "delete_permission", - "add_contenttype", "change_contenttype", "delete_contenttype", - "add_message", "change_message", "delete_message", - "add_session", "change_session", "delete_session", - "add_migrationhistory", "change_migrationhistory", "delete_migrationhistory", - "add_version", "change_version", "delete_version", - "add_revision", "change_revision", "delete_revision", - "add_user", "delete_user", - "add_project", - ] - - def get_queryset(self): - return Permission.objects.exclude(codename__in=self.excluded_codenames) - class UsersViewSet(ModelCrudViewSet): permission_classes = (IsAuthenticated,) diff --git a/taiga/users/fixtures/initial_user.json b/taiga/users/fixtures/initial_user.json index 4381031f..459fcc3f 100644 --- a/taiga/users/fixtures/initial_user.json +++ b/taiga/users/fixtures/initial_user.json @@ -4,9 +4,8 @@ "model": "users.user", "fields": { "username": "admin", - "first_name": "", - "last_name": "", - "description": "", + "full_name": "", + "bio": "", "default_language": "", "color": "", "photo": "", @@ -15,10 +14,8 @@ "default_timezone": "", "is_superuser": true, "token": "", - "is_staff": true, + "github_id": null, "last_login": "2013-04-04T07:36:09.880Z", - "groups": [], - "user_permissions": [], "password": "pbkdf2_sha256$10000$oRIbCKOL1U3w$/gaYMnOlc/GnN4mn3UUXvXpk2Hx0vvht6Uqhu46aikI=", "email": "niwi@niwi.be", "date_joined": "2013-04-01T13:48:21.711Z" diff --git a/taiga/users/migrations/0005_auto__del_field_user_first_name__del_field_user_last_name__del_field_u.py b/taiga/users/migrations/0005_auto__del_field_user_first_name__del_field_user_last_name__del_field_u.py new file mode 100644 index 00000000..052d6c44 --- /dev/null +++ b/taiga/users/migrations/0005_auto__del_field_user_first_name__del_field_user_last_name__del_field_u.py @@ -0,0 +1,282 @@ +# -*- 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 'User.notify_changes_by_me' + db.delete_column('users_user', 'notify_changes_by_me') + + # Deleting field 'User.notify_level' + db.delete_column('users_user', 'notify_level') + + # Deleting field 'User.is_staff' + db.delete_column('users_user', 'is_staff') + + + # Adding field 'User.github_id' + db.add_column('users_user', 'github_id', + self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), + keep_default=False) + + # Removing M2M table for field groups on 'User' + db.delete_table(db.shorten_name('users_user_groups')) + + # Removing M2M table for field user_permissions on 'User' + db.delete_table(db.shorten_name('users_user_user_permissions')) + + # Adding field 'User.full_name' + db.add_column('users_user', 'full_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True), + keep_default=False) + + #Copy first_name and last_name into full_name + for user in orm.Users.exclude(first_name="", last_name=""): + if user.first_name and user.last_name: + user.full_name = "{} {}".format(user.first_name, user.last_name) + elif not user.last_name: + user.full_name = user.first_name + else: + user.full_name = user.last_name + user.save() + + # Deleting field 'User.first_name' + db.delete_column('users_user', 'first_name') + + # Deleting field 'User.last_name' + db.delete_column('users_user', 'last_name') + + def backwards(self, orm): + # Adding field 'User.notify_changes_by_me' + db.add_column('users_user', 'notify_changes_by_me', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + # Adding field 'User.notify_level' + db.add_column('users_user', 'notify_level', + self.gf('django.db.models.fields.CharField')(default='all_owned_projects', max_length=32), + keep_default=False) + + # Deleting field 'User.github_id' + db.delete_column('users_user', 'github_id') + + # Adding M2M table for field groups on 'User' + m2m_table_name = db.shorten_name('users_user_groups') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('user', models.ForeignKey(orm['users.user'], null=False)), + ('group', models.ForeignKey(orm['auth.group'], null=False)) + )) + db.create_unique(m2m_table_name, ['user_id', 'group_id']) + + # Adding M2M table for field user_permissions on 'User' + m2m_table_name = db.shorten_name('users_user_user_permissions') + db.create_table(m2m_table_name, ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('user', models.ForeignKey(orm['users.user'], null=False)), + ('permission', models.ForeignKey(orm['auth.permission'], null=False)) + )) + db.create_unique(m2m_table_name, ['user_id', 'permission_id']) + + # Adding field 'User.first_name' + db.add_column('users_user', 'first_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=30, blank=True), + keep_default=False) + + # Adding field 'User.last_name' + db.add_column('users_user', 'last_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=30, blank=True), + keep_default=False) + + #Copy full_name into first_name and last_name + for user in orm.Users.exclude(full_name=""): + first_name, last_name = user.full_name.split(maxsplit=1) + + user.first_name = first_name[:30] + user.last_name = last_name[:30] + user.save() + + # Deleting field 'User.full_name' + db.delete_column('users_user', 'full_name') + + # Adding field 'User.is_staff' + db.add_column('users_user', 'is_staff', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + models = { + 'auth.permission': { + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', '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'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'db_table': "'django_content_type'", 'ordering': "('name',)", 'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)"}, + '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'}) + }, + 'projects.issuestatus': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'IssueStatus'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'issue_statuses'"}) + }, + 'projects.issuetype': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'IssueType'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'issue_types'"}) + }, + 'projects.membership': { + 'Meta': {'unique_together': "(('user', 'project'),)", 'ordering': "['project', 'role']", 'object_name': 'Membership'}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'blank': 'True', 'auto_now_add': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'memberships'"}), + 'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.Role']", 'related_name': "'memberships'"}), + 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '60', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['users.User']", 'null': 'True', 'blank': 'True', 'related_name': "'memberships'"}) + }, + 'projects.points': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'Points'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'points'"}), + 'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) + }, + 'projects.priority': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'Priority'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'priorities'"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project'}, + 'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}), + 'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['projects.ProjectTemplate']", 'null': 'True', 'blank': 'True', 'related_name': "'projects'"}), + 'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.IssueStatus']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.IssueType']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_points': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Points']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Priority']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Severity']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.TaskStatus']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.UserStoryStatus']", 'on_delete': 'models.SET_NULL', 'unique': 'True', 'blank': 'True', 'null': 'True', 'related_name': "'+'"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['users.User']", 'through': "orm['projects.Membership']", 'symmetrical': 'False', 'related_name': "'projects'"}), + 'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'related_name': "'owned_projects'"}), + 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'}), + 'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}), + 'total_milestones': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}), + 'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}) + }, + 'projects.projecttemplate': { + 'Meta': {'ordering': "['name']", 'object_name': 'ProjectTemplate'}, + 'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}), + 'default_options': ('django_pgjson.fields.JsonField', [], {}), + 'default_owner_role': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'issue_statuses': ('django_pgjson.fields.JsonField', [], {}), + 'issue_types': ('django_pgjson.fields.JsonField', [], {}), + 'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}), + 'points': ('django_pgjson.fields.JsonField', [], {}), + 'priorities': ('django_pgjson.fields.JsonField', [], {}), + 'roles': ('django_pgjson.fields.JsonField', [], {}), + 'severities': ('django_pgjson.fields.JsonField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'}), + 'task_statuses': ('django_pgjson.fields.JsonField', [], {}), + 'us_statuses': ('django_pgjson.fields.JsonField', [], {}), + 'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), + 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}) + }, + 'projects.severity': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'Severity'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'severities'"}) + }, + 'projects.taskstatus': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'TaskStatus'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'task_statuses'"}) + }, + 'projects.userstorystatus': { + 'Meta': {'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']", 'object_name': 'UserStoryStatus'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'us_statuses'"}), + 'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) + }, + 'users.role': { + 'Meta': {'unique_together': "(('slug', 'project'),)", 'ordering': "['order', 'slug']", 'object_name': 'Role'}, + 'computable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'related_name': "'roles'"}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'roles'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True'}) + }, + 'users.user': { + 'Meta': {'ordering': "['username']", 'object_name': 'User'}, + 'color': ('django.db.models.fields.CharField', [], {'default': "'#be719b'", 'max_length': '9', 'blank': 'True'}), + 'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}), + 'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'github_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'}) + } + } + + complete_apps = ['users'] diff --git a/taiga/users/migrations/0005_auto__del_field_user_notify_changes_by_me__del_field_user_notify_level.py b/taiga/users/migrations/0006_rename_description_to_bio.py similarity index 67% rename from taiga/users/migrations/0005_auto__del_field_user_notify_changes_by_me__del_field_user_notify_level.py rename to taiga/users/migrations/0006_rename_description_to_bio.py index df2c84dd..fff41c74 100644 --- a/taiga/users/migrations/0005_auto__del_field_user_notify_changes_by_me__del_field_user_notify_level.py +++ b/taiga/users/migrations/0006_rename_description_to_bio.py @@ -8,48 +8,28 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - # Deleting field 'User.notify_changes_by_me' - db.delete_column('users_user', 'notify_changes_by_me') - - # Deleting field 'User.notify_level' - db.delete_column('users_user', 'notify_level') - + db.rename_column(u'users_user', 'description', 'bio') def backwards(self, orm): - # Adding field 'User.notify_changes_by_me' - db.add_column('users_user', 'notify_changes_by_me', - self.gf('django.db.models.fields.BooleanField')(default=False), - keep_default=False) - - # Adding field 'User.notify_level' - db.add_column('users_user', 'notify_level', - self.gf('django.db.models.fields.CharField')(default='all_owned_projects', max_length=32), - keep_default=False) - + db.rename_column(u'users_user', 'bio', 'description') 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', [], {'symmetrical': 'False', 'to': "orm['auth.Permission']", 'blank': 'True'}) - }, 'auth.permission': { - 'Meta': {'object_name': 'Permission', 'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', 'codename')"}, + 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission', 'ordering': "('content_type__app_label', 'content_type__model', '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'}) }, 'contenttypes.contenttype': { - 'Meta': {'db_table': "'django_content_type'", 'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)"}, + 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'ordering': "('name',)", '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'}) }, 'projects.issuestatus': { - 'Meta': {'object_name': 'IssueStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueStatus', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -58,7 +38,7 @@ class Migration(SchemaMigration): 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_statuses'", 'to': "orm['projects.Project']"}) }, 'projects.issuetype': { - 'Meta': {'object_name': 'IssueType', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueType', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), @@ -66,17 +46,17 @@ class Migration(SchemaMigration): 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_types'", 'to': "orm['projects.Project']"}) }, 'projects.membership': { - 'Meta': {'object_name': 'Membership', 'unique_together': "(('user', 'project'),)", 'ordering': "['project', 'role']"}, - 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'default': 'datetime.datetime.now', 'blank': 'True'}), - 'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'Meta': {'unique_together': "(('user', 'project'),)", 'object_name': 'Membership', 'ordering': "['project', 'role']"}, + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'blank': 'True', 'auto_now_add': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'null': 'True', 'blank': 'True', 'max_length': '255'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['projects.Project']"}), 'role': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['users.Role']"}), - 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '60', 'null': 'True', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'memberships'", 'to': "orm['users.User']", 'null': 'True', 'blank': 'True'}) + 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'blank': 'True', 'max_length': '60'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'memberships'", 'null': 'True', 'blank': 'True', 'to': "orm['users.User']"}) }, 'projects.points': { - 'Meta': {'object_name': 'Points', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Points', 'ordering': "['project', 'order', 'name']"}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), @@ -84,7 +64,7 @@ class Migration(SchemaMigration): 'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) }, 'projects.priority': { - 'Meta': {'object_name': 'Priority', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Priority', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), @@ -93,36 +73,36 @@ class Migration(SchemaMigration): }, 'projects.project': { 'Meta': {'object_name': 'Project', 'ordering': "['name']"}, - 'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'projects'", 'to': "orm['projects.ProjectTemplate']", 'null': 'True', 'blank': 'True'}), - 'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.IssueStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.IssueType']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_points': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Points']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Priority']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Severity']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.TaskStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), - 'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.UserStoryStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}), + 'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}), + 'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'projects'", 'null': 'True', 'blank': 'True', 'to': "orm['projects.ProjectTemplate']"}), + 'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.IssueStatus']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.IssueType']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_points': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.Points']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.Priority']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.Severity']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.TaskStatus']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), + 'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'to': "orm['projects.UserStoryStatus']", 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'unique': 'True'}), 'description': ('django.db.models.fields.TextField', [], {}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'members': ('django.db.models.fields.related.ManyToManyField', [], {'through': "orm['projects.Membership']", 'related_name': "'projects'", 'to': "orm['users.User']", 'symmetrical': 'False'}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'through': "orm['projects.Membership']", 'to': "orm['users.User']", 'symmetrical': 'False', 'related_name': "'projects'"}), 'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}), 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_projects'", 'to': "orm['users.User']"}), 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '250', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True', 'unique': 'True'}), 'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}), 'total_milestones': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}), - 'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), - 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}) + 'videoconferences': ('django.db.models.fields.CharField', [], {'null': 'True', 'blank': 'True', 'max_length': '250'}), + 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'null': 'True', 'blank': 'True', 'max_length': '250'}) }, 'projects.projecttemplate': { 'Meta': {'object_name': 'ProjectTemplate', 'ordering': "['name']"}, - 'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}), 'default_options': ('django_pgjson.fields.JsonField', [], {}), 'default_owner_role': ('django.db.models.fields.CharField', [], {'max_length': '50'}), 'description': ('django.db.models.fields.TextField', [], {}), @@ -139,14 +119,14 @@ class Migration(SchemaMigration): 'priorities': ('django_pgjson.fields.JsonField', [], {}), 'roles': ('django_pgjson.fields.JsonField', [], {}), 'severities': ('django_pgjson.fields.JsonField', [], {}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '250', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True', 'unique': 'True'}), 'task_statuses': ('django_pgjson.fields.JsonField', [], {}), 'us_statuses': ('django_pgjson.fields.JsonField', [], {}), - 'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}), - 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}) + 'videoconferences': ('django.db.models.fields.CharField', [], {'null': 'True', 'blank': 'True', 'max_length': '250'}), + 'videoconferences_salt': ('django.db.models.fields.CharField', [], {'null': 'True', 'blank': 'True', 'max_length': '250'}) }, 'projects.severity': { - 'Meta': {'object_name': 'Severity', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Severity', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), @@ -154,7 +134,7 @@ class Migration(SchemaMigration): 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'severities'", 'to': "orm['projects.Project']"}) }, 'projects.taskstatus': { - 'Meta': {'object_name': 'TaskStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'TaskStatus', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -163,7 +143,7 @@ class Migration(SchemaMigration): 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_statuses'", 'to': "orm['projects.Project']"}) }, 'projects.userstorystatus': { - 'Meta': {'object_name': 'UserStoryStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"}, + 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'UserStoryStatus', 'ordering': "['project', 'order', 'name']"}, 'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -173,38 +153,37 @@ class Migration(SchemaMigration): 'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) }, 'users.role': { - 'Meta': {'object_name': 'Role', 'unique_together': "(('slug', 'project'),)", 'ordering': "['order', 'slug']"}, + 'Meta': {'unique_together': "(('slug', 'project'),)", 'object_name': 'Role', 'ordering': "['order', 'slug']"}, 'computable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), 'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'to': "orm['auth.Permission']", 'symmetrical': 'False'}), 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'roles'", 'to': "orm['projects.Project']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True'}) + 'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '250'}) }, 'users.user': { 'Meta': {'object_name': 'User', 'ordering': "['username']"}, - 'color': ('django.db.models.fields.CharField', [], {'default': "'#e6748a'", 'max_length': '9', 'blank': 'True'}), + 'bio': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'color': ('django.db.models.fields.CharField', [], {'default': "'#f18e35'", 'blank': 'True', 'max_length': '9'}), 'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}), - 'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - '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', [], {'related_name': "'user_set'", 'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'blank': 'True', 'max_length': '20'}), + 'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'blank': 'True', 'max_length': '20'}), + 'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}), + 'full_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '256'}), + 'github_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), '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'}), + 'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'notify_level': ('django.db.models.fields.CharField', [], {'default': "'all_owned_projects'", 'max_length': '32'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), - 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'user_set'", 'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + 'photo': ('django.db.models.fields.files.FileField', [], {'null': 'True', 'blank': 'True', 'max_length': '500'}), + 'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'blank': 'True', 'max_length': '200'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'}) } } - complete_apps = ['users'] \ No newline at end of file + complete_apps = ['users'] diff --git a/taiga/users/models.py b/taiga/users/models.py index 99ee552c..ee183aed 100644 --- a/taiga/users/models.py +++ b/taiga/users/models.py @@ -17,32 +17,83 @@ from django.db import models from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import UserManager, AbstractUser +from django.contrib.auth.models import UserManager, AbstractBaseUser +from django.core import validators +from django.utils import timezone from taiga.base.utils.slug import slugify_uniquely import random +import re def generate_random_hex_color(): return "#{:06x}".format(random.randint(0,0xFFFFFF)) -class User(AbstractUser): +class PermissionsMixin(models.Model): + """ + A mixin class that adds the fields and methods necessary to support + Django's Permission model using the ModelBackend. + """ + is_superuser = models.BooleanField(_('superuser status'), default=False, + help_text=_('Designates that this user has all permissions without ' + 'explicitly assigning them.')) + + class Meta: + abstract = True + + def has_perm(self, perm, obj=None): + """ + Returns True if the user is superadmin and is active + """ + return self.is_active and self.is_superuser + + def has_perms(self, perm_list, obj=None): + """ + Returns True if the user is superadmin and is active + """ + return self.is_active and self.is_superuser + + def has_module_perms(self, app_label): + """ + Returns True if the user is superadmin and is active + """ + return self.is_active and self.is_superuser + + +class User(AbstractBaseUser, PermissionsMixin): + username = models.CharField(_('username'), max_length=30, unique=True, + help_text=_('Required. 30 characters or fewer. Letters, numbers and ' + '@/./+/-/_ characters'), + validators=[ + validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid') + ]) + email = models.EmailField(_('email address'), blank=True) + is_active = models.BooleanField(_('active'), default=True, + help_text=_('Designates whether this user should be treated as ' + 'active. Unselect this instead of deleting accounts.')) + + full_name = models.CharField(_('full name'), max_length=256, blank=True) color = models.CharField(max_length=9, null=False, blank=True, default=generate_random_hex_color, verbose_name=_("color")) - description = models.TextField(null=False, blank=True, - verbose_name=_("description")) - photo = models.FileField(upload_to="files/msg", max_length=500, null=True, blank=True, + bio = models.TextField(null=False, blank=True, default="", verbose_name=_("biography")) + photo = models.FileField(upload_to="users/photo", max_length=500, null=True, blank=True, verbose_name=_("photo")) + date_joined = models.DateTimeField(_('date joined'), default=timezone.now) default_language = models.CharField(max_length=20, null=False, blank=True, default="", verbose_name=_("default language")) default_timezone = models.CharField(max_length=20, null=False, blank=True, default="", verbose_name=_("default timezone")) - token = models.CharField(max_length=200, null=True, blank=True, default=None, - verbose_name=_("token")) colorize_tags = models.BooleanField(null=False, blank=True, default=False, verbose_name=_("colorize tags")) + token = models.CharField(max_length=200, null=True, blank=True, default=None, + verbose_name=_("token")) + github_id = models.IntegerField(null=True, blank=True, verbose_name=_("github ID")) + + USERNAME_FIELD = 'username' + REQUIRED_FIELDS = ['email'] + objects = UserManager() class Meta: @@ -56,8 +107,12 @@ class User(AbstractUser): def __str__(self): return self.get_full_name() + def get_short_name(self): + "Returns the short name for the user." + return self.username + def get_full_name(self): - return super().get_full_name() or self.username or self.email + return self.full_name or self.username or self.email class Role(models.Model): @@ -104,4 +159,3 @@ def role_post_save(sender, instance, created, **kwargs): unique_projects = set(map(lambda x: x.project, instance.memberships.all())) for project in unique_projects: project.update_role_points() - diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py index 7a4e09de..58d40489 100644 --- a/taiga/users/serializers.py +++ b/taiga/users/serializers.py @@ -15,7 +15,6 @@ # along with this program. If not, see . from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import Permission from rest_framework import serializers @@ -23,20 +22,12 @@ from taiga.projects.models import Project from .models import User, Role -class PermissionSerializer(serializers.ModelSerializer): - class Meta: - model = Permission - fields = ("id", "name", "codename") - - class UserSerializer(serializers.ModelSerializer): - full_name = serializers.CharField(source="get_full_name", required=False) - class Meta: model = User - fields = ("id", "username", "first_name", "last_name", "full_name", "email", - "color", "description", "default_language", "default_timezone", - "is_active", "photo",) + fields = ('id', 'username', 'full_name', 'email', 'github_id', + 'color', 'bio', 'default_language', 'default_timezone', + 'is_active', 'photo') class RecoverySerializer(serializers.Serializer): diff --git a/taiga/users/tests/__init__.py b/taiga/users/tests/__init__.py index faf2c90a..1a9dcfcb 100644 --- a/taiga/users/tests/__init__.py +++ b/taiga/users/tests/__init__.py @@ -25,8 +25,7 @@ def create_user(id, save=True, is_superuser=False): instance = model( username="user{0}".format(id), email="user{0}@taiga.io".format(id), - first_name="Foo{0}".format(id), - last_name="Bar{0}".format(id) + full_name="Foo{0} Bar{0}".format(id) ) instance.set_password(instance.username) @@ -46,14 +45,3 @@ def create_user(id, save=True, is_superuser=False): dm.save() return instance - - -def create_domain(name, public_register=False): - domain_model = get_model("domains", "Domain") - - instance = domain_model(name=name, - domain=name, - public_register=public_register) - - instance.save() - return instance diff --git a/tests/integration/test_auth_api.py b/tests/integration/test_auth_api.py index fe1054c9..54dd4bd1 100644 --- a/tests/integration/test_auth_api.py +++ b/tests/integration/test_auth_api.py @@ -10,8 +10,7 @@ pytestmark = pytest.mark.django_db def register_form(): return {"username": "username", "password": "password", - "first_name": "fname", - "last_name": "lname", + "full_name": "fname", "email": "user@email.com", "type": "public"} diff --git a/tests/integration/test_mdrender.py b/tests/integration/test_mdrender.py index cab7b28d..121cf38d 100644 --- a/tests/integration/test_mdrender.py +++ b/tests/integration/test_mdrender.py @@ -14,7 +14,7 @@ dummy_project.slug = "test" def test_proccessor_valid_user_mention(): - factories.UserFactory(username="user1", first_name="test", last_name="name") + factories.UserFactory(username="user1", full_name="test name") result = render(dummy_project, "**@user1**") expected_result = "

@user1

" assert result == expected_result @@ -26,6 +26,6 @@ def test_proccessor_invalid_user_mention(): def test_render_and_extract_mentions(): - user = factories.UserFactory(username="user1", first_name="test", last_name="name") + user = factories.UserFactory(username="user1", full_name="test") (_, extracted) = render_and_extract(dummy_project, "**@user1**") assert extracted['mentions'] == [user] diff --git a/tests/integration/test_neighbors.py b/tests/integration/test_neighbors.py index a55dd267..5b91309e 100644 --- a/tests/integration/test_neighbors.py +++ b/tests/integration/test_neighbors.py @@ -22,12 +22,12 @@ def teardown_module(): class TestGetAttribute: def test_no_attribute(self, object): - object.first_name = "name" + object.full_name = "name" with pytest.raises(AttributeError): n.get_attribute(object, "name") with pytest.raises(AttributeError): - n.get_attribute(object, "first_name__last_name") + n.get_attribute(object, "full_name__last_name") def test_one_level(self, object): object.name = "name" @@ -35,14 +35,14 @@ class TestGetAttribute: def test_two_levels(self, object): object.name = object - object.name.first_name = "first name" - assert n.get_attribute(object, "name__first_name") == object.name.first_name + object.name.full_name = "first name" + assert n.get_attribute(object, "name__full_name") == object.name.full_name def test_three_levels(self, object): object.info = object object.info.name = object - object.info.name.first_name = "first name" - assert n.get_attribute(object, "info__name__first_name") == object.info.name.first_name + object.info.name.full_name = "first name" + assert n.get_attribute(object, "info__name__full_name") == object.info.name.full_name def test_transform_field_into_lookup(): @@ -237,14 +237,14 @@ class TestIssues: def test_ordering_by_owner(self): project = f.ProjectFactory.create() - owner1 = f.UserFactory.create(first_name="Chuck", last_name="Norris") - owner2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle") + owner1 = f.UserFactory.create(full_name="Chuck Norris") + owner2 = f.UserFactory.create(full_name="George Of The Jungle") issue1 = f.IssueFactory.create(project=project, owner=owner2) issue2 = f.IssueFactory.create(project=project, owner=owner1) issue3 = f.IssueFactory.create(project=project, owner=owner1) - issues = Issue.objects.filter(project=project).order_by("owner__first_name") + issues = Issue.objects.filter(project=project).order_by("owner__full_name") issue2_neighbors = n.get_neighbors(issue2, results_set=issues) issue3_neighbors = n.get_neighbors(issue3, results_set=issues) @@ -256,14 +256,14 @@ class TestIssues: def test_ordering_by_owner_desc(self): project = f.ProjectFactory.create() - owner1 = f.UserFactory.create(first_name="Chuck", last_name="Norris") - owner2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle") + owner1 = f.UserFactory.create(full_name="Chuck Norris") + owner2 = f.UserFactory.create(full_name="George Of The Jungle") issue1 = f.IssueFactory.create(project=project, owner=owner2) issue2 = f.IssueFactory.create(project=project, owner=owner1) issue3 = f.IssueFactory.create(project=project, owner=owner1) - issues = Issue.objects.filter(project=project).order_by("-owner__first_name") + issues = Issue.objects.filter(project=project).order_by("-owner__full_name") issue1_neighbors = n.get_neighbors(issue1, results_set=issues) issue3_neighbors = n.get_neighbors(issue3, results_set=issues) @@ -275,14 +275,14 @@ class TestIssues: def test_ordering_by_assigned_to(self): project = f.ProjectFactory.create() - assigned_to1 = f.UserFactory.create(first_name="Chuck", last_name="Norris") - assigned_to2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle") + assigned_to1 = f.UserFactory.create(full_name="Chuck Norris") + assigned_to2 = f.UserFactory.create(full_name="George Of The Jungle") issue1 = f.IssueFactory.create(project=project, assigned_to=assigned_to2) issue2 = f.IssueFactory.create(project=project, assigned_to=assigned_to1) issue3 = f.IssueFactory.create(project=project, assigned_to=assigned_to1) - issues = Issue.objects.filter(project=project).order_by("assigned_to__first_name") + issues = Issue.objects.filter(project=project).order_by("assigned_to__full_name") issue2_neighbors = n.get_neighbors(issue2, results_set=issues) issue3_neighbors = n.get_neighbors(issue3, results_set=issues) @@ -294,14 +294,14 @@ class TestIssues: def test_ordering_by_assigned_to_desc(self): project = f.ProjectFactory.create() - assigned_to1 = f.UserFactory.create(first_name="Chuck", last_name="Norris") - assigned_to2 = f.UserFactory.create(first_name="George", last_name="Of The Jungle") + assigned_to1 = f.UserFactory.create(full_name="Chuck Norris") + assigned_to2 = f.UserFactory.create(full_name="George Of The Jungle") issue1 = f.IssueFactory.create(project=project, assigned_to=assigned_to2) issue2 = f.IssueFactory.create(project=project, assigned_to=assigned_to1) issue3 = f.IssueFactory.create(project=project, assigned_to=assigned_to1) - issues = Issue.objects.filter(project=project).order_by("-assigned_to__first_name") + issues = Issue.objects.filter(project=project).order_by("-assigned_to__full_name") issue1_neighbors = n.get_neighbors(issue1, results_set=issues) issue3_neighbors = n.get_neighbors(issue3, results_set=issues)