summaryrefslogtreecommitdiff
path: root/app/accounts
diff options
context:
space:
mode:
Diffstat (limited to 'app/accounts')
-rw-r--r--app/accounts/__init__.py1
-rw-r--r--app/accounts/admin.py13
-rw-r--r--app/accounts/apps.py8
-rw-r--r--app/accounts/forms.py28
-rw-r--r--app/accounts/migrations/0001_initial.py55
-rw-r--r--app/accounts/migrations/0002_auto_20190108_2115.py35
-rw-r--r--app/accounts/migrations/__init__.py0
-rw-r--r--app/accounts/models.py33
-rw-r--r--app/accounts/signals.py12
-rw-r--r--app/accounts/tests/__init__.py0
-rw-r--r--app/accounts/tests/test_models.py13
-rw-r--r--app/accounts/tests/test_views.py23
-rw-r--r--app/accounts/urls.py18
-rw-r--r--app/accounts/views.py32
14 files changed, 271 insertions, 0 deletions
diff --git a/app/accounts/__init__.py b/app/accounts/__init__.py
new file mode 100644
index 0000000..9332741
--- /dev/null
+++ b/app/accounts/__init__.py
@@ -0,0 +1 @@
+default_app_config = 'accounts.apps.AccountAppConfig'
diff --git a/app/accounts/admin.py b/app/accounts/admin.py
new file mode 100644
index 0000000..25f873b
--- /dev/null
+++ b/app/accounts/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from .models import User, UserProfile
+
+
+@admin.register(User)
+class UserAdmin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(UserProfile)
+class UserProfileAdmin(admin.ModelAdmin):
+ pass
diff --git a/app/accounts/apps.py b/app/accounts/apps.py
new file mode 100644
index 0000000..9a7d5ba
--- /dev/null
+++ b/app/accounts/apps.py
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+
+class AccountAppConfig(AppConfig):
+ name = 'accounts'
+
+ def ready(self):
+ import accounts.signals
diff --git a/app/accounts/forms.py b/app/accounts/forms.py
new file mode 100644
index 0000000..2da08cb
--- /dev/null
+++ b/app/accounts/forms.py
@@ -0,0 +1,28 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django_registration.forms import RegistrationForm
+
+from .models import User, UserProfile
+
+
+class UserForm(RegistrationForm):
+ class Meta(RegistrationForm.Meta):
+ model = User
+
+
+class ProfileForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ fields = ['bio', 'photo', 'website']
+ labels = {
+ "photo": _("Profile photo"),
+ "bio": _("Bio. A little about you. links are fine, line breaks are not. Keep it short and sweet, 350 characters max"),
+ "website": _("If you have a personal website, plug it in here."),
+ }
+ widgets = {
+ 'bio': forms.Textarea(attrs={'cols': 104, 'rows': 10, 'class': 'textarea-rounded'}),
+ }
+
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop("user", None)
+ super(ProfileForm, self).__init__(*args, **kwargs)
diff --git a/app/accounts/migrations/0001_initial.py b/app/accounts/migrations/0001_initial.py
new file mode 100644
index 0000000..36aa6ba
--- /dev/null
+++ b/app/accounts/migrations/0001_initial.py
@@ -0,0 +1,55 @@
+# Generated by Django 2.1.2 on 2018-11-24 04:41
+
+from django.conf import settings
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('auth', '0009_alter_user_last_name_max_length'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
+ ],
+ options={
+ 'ordering': ['-date_joined'],
+ },
+ managers=[
+ ('objects', django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='UserProfile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('photo', models.ImageField(blank=True, null=True, upload_to='profile')),
+ ('website', models.CharField(blank=True, default='', max_length=300, null=True)),
+ ('location', models.CharField(blank=True, default='', max_length=300, null=True)),
+ ('bio', models.TextField(blank=True, default='', null=True)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/app/accounts/migrations/0002_auto_20190108_2115.py b/app/accounts/migrations/0002_auto_20190108_2115.py
new file mode 100644
index 0000000..1ebb280
--- /dev/null
+++ b/app/accounts/migrations/0002_auto_20190108_2115.py
@@ -0,0 +1,35 @@
+# Generated by Django 2.1.2 on 2019-01-09 03:15
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userprofile',
+ name='bio',
+ field=models.CharField(blank=True, default='', max_length=350),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='location',
+ field=models.CharField(blank=True, default='', max_length=300),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='user',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='website',
+ field=models.CharField(blank=True, default='', max_length=300),
+ ),
+ ]
diff --git a/app/accounts/migrations/__init__.py b/app/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/accounts/migrations/__init__.py
diff --git a/app/accounts/models.py b/app/accounts/models.py
new file mode 100644
index 0000000..feb20bf
--- /dev/null
+++ b/app/accounts/models.py
@@ -0,0 +1,33 @@
+from django.db import models
+from django.urls import reverse
+from django.contrib.auth.models import AbstractUser
+from django.utils.functional import cached_property
+
+from notes.models import Notebook
+
+
+class User(AbstractUser):
+ pass
+
+ class Meta:
+ ordering = ['-date_joined']
+
+
+class UserProfile(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
+ photo = models.ImageField(upload_to='profile', null=True, blank=True)
+ website = models.CharField(max_length=300, blank=True, default='')
+ location = models.CharField(max_length=300, blank=True, default='')
+ bio = models.CharField(max_length=350, blank=True, default='')
+ #default_note_folder = models.ForeignKey('notes.Notebook', null=True, on_delete=models.SET_NULL)
+ #default_note_public = models.BooleanField(default=False)
+
+ def __str__(self):
+ return self.user.username
+
+ def get_absolute_url(self):
+ return reverse("accounts:settings")
+
+ @cached_property
+ def get_notebook_list(self):
+ return Notebook.objects.filter(owner=self.user).select_related().annotate(note_count=models.Count('note'))[:8]
diff --git a/app/accounts/signals.py b/app/accounts/signals.py
new file mode 100644
index 0000000..837a7ed
--- /dev/null
+++ b/app/accounts/signals.py
@@ -0,0 +1,12 @@
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+from .models import User, UserProfile
+
+
+@receiver(post_save, sender=User)
+def create_profile(sender, update_fields, created, instance, **kwargs):
+ """ creates a blank profile when a new user signs up """
+ if created:
+ user_profile = UserProfile.objects.create(user=instance)
+ user_profile.save()
diff --git a/app/accounts/tests/__init__.py b/app/accounts/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/accounts/tests/__init__.py
diff --git a/app/accounts/tests/test_models.py b/app/accounts/tests/test_models.py
new file mode 100644
index 0000000..f9b33f6
--- /dev/null
+++ b/app/accounts/tests/test_models.py
@@ -0,0 +1,13 @@
+from django.test import TestCase
+from mixer.backend.django import mixer
+
+from accounts.models import User, UserProfile
+
+
+class UserProfileModelTest(TestCase):
+
+ def test_string_representation(self):
+ user = mixer.blend(User, username='test')
+ user.save()
+ profile = UserProfile.objects.get(user=user)
+ self.assertEqual(str(profile), str(user.username))
diff --git a/app/accounts/tests/test_views.py b/app/accounts/tests/test_views.py
new file mode 100644
index 0000000..39dcb31
--- /dev/null
+++ b/app/accounts/tests/test_views.py
@@ -0,0 +1,23 @@
+from django.test import Client
+from django.test import RequestFactory, TestCase
+from mixer.backend.django import mixer
+
+from accounts.models import User
+from accounts.views import ProfileView
+
+
+class ProfileViewTest(TestCase):
+ def setUp(self):
+ # Every test needs access to the request factory.
+ self.factory = RequestFactory()
+ self.user = mixer.blend(User, username='tpynchon', password="gravity")
+
+ def test_profile_view(self):
+ request = self.factory.get('/settings/')
+ request.user = self.user
+ response = ProfileView.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+ response.render()
+ html = response.content.decode('utf8')
+ self.assertTrue(html.startswith('<!DOCTYPE html>'))
+ self.assertIn('<h1>Account Settings</h1>', html)
diff --git a/app/accounts/urls.py b/app/accounts/urls.py
new file mode 100644
index 0000000..5cad311
--- /dev/null
+++ b/app/accounts/urls.py
@@ -0,0 +1,18 @@
+from django.urls import path
+
+from . import views
+
+app_name = "accounts"
+
+urlpatterns = [
+ path(
+ r'change-profile/',
+ views.ProfileView.as_view(),
+ name="change-profile"
+ ),
+ path(
+ r'',
+ views.SettingsListView.as_view(),
+ name="settings"
+ ),
+]
diff --git a/app/accounts/views.py b/app/accounts/views.py
new file mode 100644
index 0000000..75bb933
--- /dev/null
+++ b/app/accounts/views.py
@@ -0,0 +1,32 @@
+from django.views.generic import UpdateView, DetailView
+from django.utils.decorators import method_decorator
+from django.contrib.auth.decorators import login_required
+
+from .models import UserProfile
+from .forms import ProfileForm
+
+
+@method_decorator(login_required, name='dispatch')
+class UpdateViewWithUser(UpdateView):
+
+ def get_form_kwargs(self, **kwargs):
+ kwargs = super().get_form_kwargs(**kwargs)
+ kwargs.update({'user': self.request.user})
+ return kwargs
+
+
+class ProfileView(UpdateViewWithUser):
+ model = UserProfile
+ form_class = ProfileForm
+ template_name = "accounts/change-settings.html"
+
+ def get_object(self):
+ return self.request.user.profile
+
+
+class SettingsListView(DetailView):
+ model = UserProfile
+ template_name = "accounts/profile.html"
+
+ def get_object(self):
+ return self.request.user.profile