From 2d6d2c0e657f1037d419d365f5434153ed55857e Mon Sep 17 00:00:00 2001 From: Daniel Greenfeld Date: Fri, 16 Aug 2013 13:54:47 +0200 Subject: [PATCH] So much boilerplate --- {{cookiecutter.repo_name}}/users/__init__.py | 0 {{cookiecutter.repo_name}}/users/admin.py | 13 ++ {{cookiecutter.repo_name}}/users/forms.py | 14 ++ .../users/migrations/0001_initial.py | 96 +++++++++++ .../users/migrations/__init__.py | 0 {{cookiecutter.repo_name}}/users/models.py | 15 ++ {{cookiecutter.repo_name}}/users/urls.py | 31 ++++ {{cookiecutter.repo_name}}/users/views.py | 60 +++++++ .../config/settings.py | 159 ++++++++++++++++-- .../{{cookiecutter.repo_name}}/config/urls.py | 10 +- .../{{cookiecutter.repo_name}}/config/wsgi.py | 15 +- 11 files changed, 378 insertions(+), 35 deletions(-) create mode 100644 {{cookiecutter.repo_name}}/users/__init__.py create mode 100644 {{cookiecutter.repo_name}}/users/admin.py create mode 100644 {{cookiecutter.repo_name}}/users/forms.py create mode 100644 {{cookiecutter.repo_name}}/users/migrations/0001_initial.py create mode 100644 {{cookiecutter.repo_name}}/users/migrations/__init__.py create mode 100644 {{cookiecutter.repo_name}}/users/models.py create mode 100644 {{cookiecutter.repo_name}}/users/urls.py create mode 100644 {{cookiecutter.repo_name}}/users/views.py diff --git a/{{cookiecutter.repo_name}}/users/__init__.py b/{{cookiecutter.repo_name}}/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.repo_name}}/users/admin.py b/{{cookiecutter.repo_name}}/users/admin.py new file mode 100644 index 00000000..d91bb913 --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/admin.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from django.contrib.auth.forms import UserCreationForm, UserChangeForm + +from .models import User + + +class UserAdmin(admin.ModelAdmin): + create_form_class = UserCreationForm + update_form_class = UserChangeForm + + +admin.site.register(User, UserAdmin) diff --git a/{{cookiecutter.repo_name}}/users/forms.py b/{{cookiecutter.repo_name}}/users/forms.py new file mode 100644 index 00000000..c4f4d1fb --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/forms.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from django import forms + +from .models import User + + +class UserForm(forms.ModelForm): + + class Meta: + # Set this form to use the User model. + model = User + + # Constrain the UserForm to just these fields. + fields = ("first_name", "last_name") diff --git a/{{cookiecutter.repo_name}}/users/migrations/0001_initial.py b/{{cookiecutter.repo_name}}/users/migrations/0001_initial.py new file mode 100644 index 00000000..ca4170cf --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/migrations/0001_initial.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'User' + db.create_table(u'users_user', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('password', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('last_login', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + ('is_superuser', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)), + ('first_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)), + ('last_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)), + ('email', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)), + ('is_staff', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)), + ('date_joined', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + )) + db.send_create_signal(u'users', ['User']) + + # Adding M2M table for field groups on 'User' + m2m_table_name = db.shorten_name(u'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[u'users.user'], null=False)), + ('group', models.ForeignKey(orm[u'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(u'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[u'users.user'], null=False)), + ('permission', models.ForeignKey(orm[u'auth.permission'], null=False)) + )) + db.create_unique(m2m_table_name, ['user_id', 'permission_id']) + + + def backwards(self, orm): + # Deleting model 'User' + db.delete_table(u'users_user') + + # Removing M2M table for field groups on 'User' + db.delete_table(db.shorten_name(u'users_user_groups')) + + # Removing M2M table for field user_permissions on 'User' + db.delete_table(db.shorten_name(u'users_user_user_permissions')) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'users.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + } + } + + complete_apps = ['users'] \ No newline at end of file diff --git a/{{cookiecutter.repo_name}}/users/migrations/__init__.py b/{{cookiecutter.repo_name}}/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/{{cookiecutter.repo_name}}/users/models.py b/{{cookiecutter.repo_name}}/users/models.py new file mode 100644 index 00000000..fab73f1d --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/models.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Import the AbstractUser model +from django.contrib.auth.models import AbstractUser + +# Import the basic Django ORM models library +from django.db import models + +from django.utils.translation import ugettext_lazy as _ + + +# Subclass AbstractUser +class User(AbstractUser): + + def __unicode__(self): + return self.username diff --git a/{{cookiecutter.repo_name}}/users/urls.py b/{{cookiecutter.repo_name}}/users/urls.py new file mode 100644 index 00000000..c428549d --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/urls.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import patterns, url + +from users import views + +urlpatterns = patterns('', + # URL pattern for the UserListView + url( + regex=r'^$', + view=views.UserListView.as_view(), + name='list' + ), + # URL pattern for the UserRedirectView + url( + regex=r'^~redirect/$', + view=views.UserRedirectView.as_view(), + name='redirect' + ), + # URL pattern for the UserDetailView + url( + regex=r'^(?P[\w\-_]+)/$', + view=views.UserDetailView.as_view(), + name='detail' + ), + # URL pattern for the UserDetailView + url( + regex=r'^~update/$', + view=views.UserUpdateView.as_view(), + name='update' + ), +) \ No newline at end of file diff --git a/{{cookiecutter.repo_name}}/users/views.py b/{{cookiecutter.repo_name}}/users/views.py new file mode 100644 index 00000000..3f5c7783 --- /dev/null +++ b/{{cookiecutter.repo_name}}/users/views.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Import the reverse lookup function +from django.core.urlresolvers import reverse +from django.db.models import Count, Sum + +# view imports +from django.views.generic import DetailView +from django.views.generic import RedirectView +from django.views.generic import UpdateView +from django.views.generic import ListView + +# Only authenticated users can access views using this. +from braces.views import LoginRequiredMixin + +# Import the form from users/forms.py +from .forms import UserForm + +from djstripe.mixins import SubscriptionPaymentRequiredMixin + +# Import the customized User model +from .models import User + + +class UserDetailView(LoginRequiredMixin, DetailView): + model = User + # These next two lines tell the view to index lookups by username + slug_field = "username" + slug_url_kwarg = "username" + + +class UserRedirectView(LoginRequiredMixin, RedirectView): + permanent = False + + def get_redirect_url(self): + return reverse("users:detail", + kwargs={"username": self.request.user.username}) + + +class UserUpdateView(LoginRequiredMixin, UpdateView): + + form_class = UserForm + + # we already imported User in the view code above, remember? + model = User + + # send the user back to their own page after a successful update + def get_success_url(self): + return reverse("users:detail", + kwargs={"username": self.request.user.username}) + + def get_object(self): + # Only get the User record for the user making the request + return User.objects.get(username=self.request.user.username) + + +class UserListView(LoginRequiredMixin, SubscriptionPaymentRequiredMixin, ListView): + model = User + # These next two lines tell the view to index lookups by username + slug_field = "username" + slug_url_kwarg = "username" diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/settings.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/settings.py index 0d7ca2bc..1757105b 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/settings.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/settings.py @@ -14,6 +14,15 @@ import os from os.path import abspath, basename, dirname, join, normpath BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +if os.environ.get("DATABASE_URL", None): + ########## DEBUG CONFIGURATION + # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug + DEBUG = False + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug + TEMPLATE_DEBUG = DEBUG + ########## END DEBUG CONFIGURATION + ########## SECRET CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key @@ -21,12 +30,6 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__)) SECRET_KEY = "CHANGE THIS!!!" ########## END SECRET CONFIGURATION -########## SITE CONFIGURATION -# Hosts/domain names that are valid for this site -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["*", ] -########## END SITE CONFIGURATION - ########## FIXTURE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS @@ -36,15 +39,6 @@ FIXTURE_DIRS = ( ########## END FIXTURE CONFIGURATION -########## DEBUG CONFIGURATION -# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug -DEBUG = False - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug -TEMPLATE_DEBUG = DEBUG -########## END DEBUG CONFIGURATION - - ########## MANAGER CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins ADMINS = ( @@ -99,6 +93,18 @@ MEDIA_ROOT = join(BASE_DIR, 'media') MEDIA_URL = '/media/' ########## END MEDIA CONFIGURATION +########## MIDDLEWARE CONFIGURATION +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + #'djstripe.middleware.SubscriptionPaymentMiddleware', # TODO fix this by settings + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) +########## END MIDDLEWARE CONFIGURATION + ########## STATIC FILE CONFIGURATION # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root @@ -154,6 +160,51 @@ TEMPLATE_LOADERS = ( ) ########## END TEMPLATE CONFIGURATION +########## APP CONFIGURATION +DJANGO_APPS = ( + # Default Django apps: + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + # Useful template tags: + # 'django.contrib.humanize', + + # Admin + 'django.contrib.admin', +) +THIRD_PARTY_APPS = ( + 'south', # Database migration helpers: + 'crispy_forms', # Form layouts + # 'avatar', # for user avatars +) + +# Apps specific for this project go here. +LOCAL_APPS = ( + 'users', # custom users app +) + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + +INSTALLED_APPS += ( + # Needs to come last for now because of a weird edge case between + # South and allauth + 'allauth', # registration + 'allauth.account', # registration + # 'allauth.socialaccount', # registration +) +########## END APP CONFIGURATION + + +########## URL Configuration +ROOT_URLCONF = 'cl.urls' + +WSGI_APPLICATION = 'cl.wsgi.application' +########## End URL Configuration ########## django-secure @@ -189,4 +240,80 @@ ACCOUNT_EMAIL_VERIFICATION = "mandatory" # Select the correct user model AUTH_USER_MODEL = "users.User" LOGIN_REDIRECT_URL = "users:redirect" -########## END Custom user app defaults \ No newline at end of file +########## END Custom user app defaults + + +########## SLUGLIFIER +AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify" +########## END SLUGLIFIER + + +################## PRODUCTION SETTINGS +if DEBUG: + EMAIL_HOST = "localhost" + EMAIL_PORT = 1025 + MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) + INSTALLED_APPS += ('debug_toolbar',) + + INTERNAL_IPS = ('127.0.0.1',) + + DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': False, + 'SHOW_TEMPLATE_CONTEXT': True, + } +else: + + TEMPLATE_DEBUG = DEBUG + + ########## SITE CONFIGURATION + # Hosts/domain names that are valid for this site + # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts + ALLOWED_HOSTS = ["*"] + ########## END SITE CONFIGURATION + + INSTALLED_APPS += ("gunicorn", ) + + ########## STORAGE CONFIGURATION + from S3 import CallingFormat + from os import environ + # See: http://django-storages.readthedocs.org/en/latest/index.html + INSTALLED_APPS += ( + 'storages', + ) + + # See: http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html#settings + STATICFILES_STORAGE = DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' + + # See: http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html#settings + AWS_CALLING_FORMAT = CallingFormat.SUBDOMAIN + + # See: http://django-storages.readthedocs.org/en/latest/backends/amazon-S3.html#settings + AWS_ACCESS_KEY_ID = environ.get('AWS_ACCESS_KEY_ID', '') + AWS_SECRET_ACCESS_KEY = environ.get('AWS_SECRET_ACCESS_KEY', '') + AWS_STORAGE_BUCKET_NAME = environ.get('AWS_STORAGE_BUCKET_NAME', '') + AWS_AUTO_CREATE_BUCKET = True + AWS_QUERYSTRING_AUTH = False + + # AWS cache settings, don't change unless you know what you're doing: + AWS_EXPIREY = 60 * 60 * 24 * 7 + AWS_HEADERS = { + 'Cache-Control': 'max-age=%d, s-maxage=%d, must-revalidate' % (AWS_EXPIREY, + AWS_EXPIREY) + } + + # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url + STATIC_URL = 'https://s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME + ########## END STORAGE CONFIGURATION + + ########## EMAIL + DEFAULT_FROM_EMAIL = environ.get('DEFAULT_FROM_EMAIL', + '{{cookiecutter.project_name <{{cookiecutter.project_name-noreply@cheeseland.com>') + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = environ.get('EMAIL_HOST', 'smtp.sendgrid.com') + EMAIL_HOST_PASSWORD = os.environ.get('SENDGRID_PASSWORD', '') + EMAIL_HOST_USER = os.environ.get('SENDGRID_USERNAME', '') + EMAIL_PORT = environ.get('EMAIL_PORT', 587) + EMAIL_SUBJECT_PREFIX = environ.get('EMAIL_SUBJECT_PREFIX', '[{{cookiecutter.project_name}}] ') + EMAIL_USE_TLS = True + SERVER_EMAIL = EMAIL_HOST_USER + ########## END EMAIL diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/urls.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/urls.py index 7de2afa6..3737cb32 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/urls.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/urls.py @@ -8,13 +8,9 @@ admin.autodiscover() urlpatterns = patterns('', url(r'^$', TemplateView.as_view(template_name='base.html')), - # Examples: - # url(r'^$', '{{ project_name }}.views.home', name='home'), - # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), - - # Uncomment the admin/doc line below to enable admin documentation: - # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - # Uncomment the next line to enable the admin: url(r'^admin/', include(admin.site.urls)), + + # Uncomment the next line to enable the admin: + url(r'^users/', include("users.urls", namespace="users")), ) diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/wsgi.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/wsgi.py index f1b5a829..52691b0a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/wsgi.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/config/wsgi.py @@ -20,18 +20,9 @@ from sys import path SITE_ROOT = dirname(dirname(abspath(__file__))) path.append(SITE_ROOT) -# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks -# if running multiple sites in the same mod_wsgi process. To fix this, use -# mod_wsgi daemon mode with each site in its own daemon process, or use -# os.environ["DJANGO_SETTINGS_MODULE"] = "jajaja.settings" -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.production") -# This application object is used by any WSGI server configured to use this -# file. This includes Django's development server, if the WSGI_APPLICATION -# setting points here. +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ cookiecutter.repo_name }}.settings") + + from django.core.wsgi import get_wsgi_application application = get_wsgi_application() - -# Apply WSGI middleware here. -# from helloworld.wsgi import HelloWorldApplication -# application = HelloWorldApplication(application)