From 68abc78a9a543697d8f176775a052c1d70e371a4 Mon Sep 17 00:00:00 2001 From: luxagraf Date: Fri, 2 Nov 2018 15:01:05 -0600 Subject: added some basic user models and tests --- .coveragerc | 3 + .gitignore | 8 ++ README.md | 35 ++++++++ apps/accounts/__init__.py | 0 apps/accounts/migrations/0001_initial.py | 55 +++++++++++++ apps/accounts/migrations/__init__.py | 0 apps/accounts/models.py | 20 +++++ apps/accounts/signals.py | 12 +++ apps/accounts/tests/__init__.py | 0 apps/accounts/tests/test_models.py | 12 +++ apps/accounts/tests/test_views.py | 0 config/__init__.py | 0 config/base_urls.py | 21 +++++ config/djadmin.sh | 10 +++ config/requirements.txt | 30 +++++++ config/settings.py | 135 +++++++++++++++++++++++++++++++ config/wsgi.py | 16 ++++ manage.py | 17 ++++ 18 files changed, 374 insertions(+) create mode 100644 .coveragerc create mode 100644 .gitignore create mode 100644 README.md create mode 100644 apps/accounts/__init__.py create mode 100644 apps/accounts/migrations/0001_initial.py create mode 100644 apps/accounts/migrations/__init__.py create mode 100644 apps/accounts/models.py create mode 100644 apps/accounts/signals.py create mode 100644 apps/accounts/tests/__init__.py create mode 100644 apps/accounts/tests/test_models.py create mode 100644 apps/accounts/tests/test_views.py create mode 100644 config/__init__.py create mode 100644 config/base_urls.py create mode 100755 config/djadmin.sh create mode 100644 config/requirements.txt create mode 100644 config/settings.py create mode 100644 config/wsgi.py create mode 100755 manage.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..ea53d98 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +omit= + venv/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e506cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.log +Vagrantfile +.vagrant +.env +venv/ +__pycache__ +.coverage +htmlcov/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..108d74c --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Project purpose + +## Internal Apps +### Accounts +* adds profile to default Django user + * profile photo + * website + * bio + * location + + +## Rational for external dependencies +### Coverage +[https://github.com/nedbat/coveragepy](https://github.com/nedbat/coveragepy) +#### Why +* gotta test +* I hate keeping track of what needs tests +### Python Decouple +[https://pypi.org/project/python-decouple/](https://pypi.org/project/python-decouple/) +####Why +* Decouple helps you to organize your settings so that you can change parameters without having to redeploy your app. +* Allows committing settings.py by keeping not-committed settings in one place (.env) + +### Django Taggit +[https://github.com/alex/django-taggit](https://github.com/alex/django-taggit) +####Why +* Simplest way to get robust tagging capabilities +* When I wrote my own, this is what I wrote + + +### Django Storages +[https://github.com/jschneier/django-storages](https://github.com/jschneier/django-storages) +####Why +* Simplest way to make all static assets go to Amazon S3 +* Simplifies migrating server setups by keep static assets off the server diff --git a/apps/accounts/__init__.py b/apps/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..0e2775e --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -0,0 +1,55 @@ +# Generated by Django 2.1.2 on 2018-11-02 19:01 + +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/apps/accounts/migrations/__init__.py b/apps/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/models.py b/apps/accounts/models.py new file mode 100644 index 0000000..a930081 --- /dev/null +++ b/apps/accounts/models.py @@ -0,0 +1,20 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser + + +class User(AbstractUser): + pass + + class Meta: + ordering = ['-date_joined'] + + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + photo = models.ImageField(upload_to='profile', null=True, blank=True) + website = models.CharField(max_length=300, null=True, blank=True, default='') + location = models.CharField(max_length=300, null=True, blank=True, default='') + bio = models.TextField(null=True, blank=True, default='') + + def __str__(self): + return self.user.username diff --git a/apps/accounts/signals.py b/apps/accounts/signals.py new file mode 100644 index 0000000..5aac623 --- /dev/null +++ b/apps/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(user=instance) + user_profile.save() diff --git a/apps/accounts/tests/__init__.py b/apps/accounts/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/accounts/tests/test_models.py b/apps/accounts/tests/test_models.py new file mode 100644 index 0000000..48cc976 --- /dev/null +++ b/apps/accounts/tests/test_models.py @@ -0,0 +1,12 @@ +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') + profile = UserProfile(user=user) + self.assertEqual(str(profile), str(user.username)) diff --git a/apps/accounts/tests/test_views.py b/apps/accounts/tests/test_views.py new file mode 100644 index 0000000..e69de29 diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/base_urls.py b/config/base_urls.py new file mode 100644 index 0000000..5779be7 --- /dev/null +++ b/config/base_urls.py @@ -0,0 +1,21 @@ +"""myproj URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/config/djadmin.sh b/config/djadmin.sh new file mode 100755 index 0000000..24081d4 --- /dev/null +++ b/config/djadmin.sh @@ -0,0 +1,10 @@ +#!/bin/bash +DIR="$( cd "$( dirname $( dirname "${BASH_SOURCE[0]}" ) )" && pwd )" +PYTHONPATH=$PYTHONPATH:$DIR +PYTHONPATH=$PYTHONPATH:"${DIR}/apps" +PYTHONPATH=$PYTHONPATH:"${DIR}/apps/lib" +PYTHONPATH=$PYTHONPATH:"${DIR}/venv/lib/python3.6/" +export PYTHONPATH +export DJANGO_SETTINGS_MODULE=config.settings +ADMIN="${DIR}/venv/bin/django-admin.py" +$ADMIN $@ diff --git a/config/requirements.txt b/config/requirements.txt new file mode 100644 index 0000000..d89255f --- /dev/null +++ b/config/requirements.txt @@ -0,0 +1,30 @@ +backcall==0.1.0 +confusable-homoglyphs==3.2.0 +coverage==4.5.1 +decorator==4.3.0 +Django==2.1.2 +django-extensions==2.1.3 +django-registration==3.0 +django-storages==1.7.1 +django-taggit==0.23.0 +Faker==0.9.1 +ipython==7.1.1 +ipython-genutils==0.2.0 +jedi==0.13.1 +mixer==6.1.3 +parso==0.3.1 +pexpect==4.6.0 +pickleshare==0.7.5 +Pillow==5.3.0 +pkg-resources==0.0.0 +prompt-toolkit==2.0.7 +psycopg2==2.7.5 +ptyprocess==0.6.0 +Pygments==2.2.0 +python-dateutil==2.7.5 +python-decouple==3.1 +pytz==2018.7 +six==1.11.0 +text-unidecode==1.2 +traitlets==4.3.2 +wcwidth==0.1.7 diff --git a/config/settings.py b/config/settings.py new file mode 100644 index 0000000..abe86dd --- /dev/null +++ b/config/settings.py @@ -0,0 +1,135 @@ +""" +Django settings for project. + +Generated by 'django-admin startproject' using Django 2.1.2. + +For more information on this file, see +https://docs.djangoproject.com/en/2.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.1/ref/settings/ +""" + +import os +from decouple import config, Csv + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +SECRET_KEY = config('SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = config('DEBUG', cast=bool) + +AUTH_PROFILE_MODULE = "accounts.UserProfile" +AUTH_USER_MODEL = "accounts.User" + +DEFAULT_FILE_STORAGE = config('DEFAULT_FILE_STORAGE') + +AWS_S3_OBJECT_PARAMETERS = { + 'CacheControl': 'max-age=86400', +} + +ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv()) + + +# APPS +# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +DJANGO_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] +THIRD_PARTY_APPS = [ + 'taggit', + 'django_extensions' +] +LOCAL_APPS = [ + 'accounts', +] +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + + +# https://docs.djangoproject.com/en/dev/ref/settings/#middleware +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'config.base_urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'config.wsgi.application' + +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': config('DB_HOST'), + 'PORT': '', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.1/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100644 index 0000000..0c727f0 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for myproj project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproj.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..e9c4109 --- /dev/null +++ b/manage.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +import os +import sys +d = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(d+"/apps") +sys.path.append(d+"/config") +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) -- cgit v1.2.3-70-g09d2