Merge branch 'master' into pyup-pin-mock-2.0.0

This commit is contained in:
Oliver Marks 2017-02-02 19:01:25 +00:00 committed by GitHub
commit cb312b34e5
46 changed files with 609 additions and 391 deletions

View File

@ -6,7 +6,12 @@ pipeline:
- USE_DOCKER=yes - USE_DOCKER=yes
- DJANGO_SETTINGS_MODULE=config.settings.test - DJANGO_SETTINGS_MODULE=config.settings.test
commands: commands:
- python manage.py test mhackspace.subscriptions --verbosity 2 - cp -n env.example .env
- python manage.py test mhackspace --verbosity 2
deploy:
#volumes: #volumes:
# postgres_data_dev: {} # postgres_data_dev: {}

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
sudo: true
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb
- sudo apt-get install -qq libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms1-dev libwebp-dev
- sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip
- sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
language: python
python:
- "3.5"

6
circle.yml Normal file
View File

@ -0,0 +1,6 @@
machine:
python:
version: 3.5.0
environment:
DJANGO_SETTINGS_MODULE: config.settings.test
DATABASE_URL: postgres://ubuntu:@127.0.0.1:5432/circle_test

View File

@ -11,6 +11,7 @@ RUN pip install -r /requirements/production.txt \
COPY . /app COPY . /app
RUN chown -R django /app RUN chown -R django /app
RUN mkdir -p /data/sockets
COPY ./compose/django/gunicorn.sh /gunicorn.sh COPY ./compose/django/gunicorn.sh /gunicorn.sh
COPY ./compose/django/entrypoint.sh /entrypoint.sh COPY ./compose/django/entrypoint.sh /entrypoint.sh
@ -19,7 +20,8 @@ RUN sed -i 's/\r//' /entrypoint.sh \
&& chmod +x /entrypoint.sh \ && chmod +x /entrypoint.sh \
&& chown django /entrypoint.sh \ && chown django /entrypoint.sh \
&& chmod +x /gunicorn.sh \ && chmod +x /gunicorn.sh \
&& chown django /gunicorn.sh && chown django /gunicorn.sh \
&& chown django /data/sockets
WORKDIR /app WORKDIR /app

View File

@ -33,5 +33,4 @@ until postgres_ready; do
sleep 1 sleep 1
done done
>&2 echo "Postgres is up - continuing..."
exec $cmd exec $cmd

View File

@ -1,3 +1,6 @@
#!/bin/sh #!/bin/sh
python /app/manage.py collectstatic --noinput python /app/manage.py collectstatic --noinput
/usr/local/bin/gunicorn config.wsgi -w 4 -b 0.0.0.0:5000 --chdir=/app chmod 777 -R /data/sockets/
touch /data/sockets/gunicron.sock
ls -la /data/sockets/
/usr/local/bin/gunicorn config.wsgi -w 4 -b unix:/data/sockets/gunicorn.sock --chdir=/app

View File

@ -4,6 +4,6 @@ ADD nginx.conf /etc/nginx/nginx.conf
ADD start.sh /start.sh ADD start.sh /start.sh
ADD nginx-secure.conf /etc/nginx/nginx-secure.conf ADD nginx-secure.conf /etc/nginx/nginx-secure.conf
ADD dhparams.pem /etc/ssl/private/dhparams.pem #ADD dhparams.pem /etc/ssl/private/dhparams.pem
CMD /start.sh CMD /start.sh

View File

@ -26,7 +26,8 @@ http {
#gzip on; #gzip on;
upstream app { upstream app {
server django:5000; #server django:5000;
server unix:/data/sockets/gunicorn.sock;
} }
server { server {
@ -36,12 +37,12 @@ http {
server_name ___my.example.com___ ; server_name ___my.example.com___ ;
location /.well-known/acme-challenge { # location /.well-known/acme-challenge {
proxy_pass http://certbot:80; # proxy_pass http://certbot:80;
proxy_set_header Host $host; # proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr; # proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https; # proxy_set_header X-Forwarded-Proto https;
} # }
location / { location / {

View File

@ -16,7 +16,7 @@ ROOT_DIR = environ.Path(__file__) - 3 # (mhackspace/config/settings/common.py -
APPS_DIR = ROOT_DIR.path('mhackspace') APPS_DIR = ROOT_DIR.path('mhackspace')
env = environ.Env() env = environ.Env()
env.read_env() env.read_env('%s/.env' % ROOT_DIR)
# APP CONFIGURATION # APP CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -48,6 +48,7 @@ LOCAL_APPS = (
# custom users app # custom users app
# Your stuff: custom apps go here # Your stuff: custom apps go here
'mhackspace.users.apps.UsersConfig', 'mhackspace.users.apps.UsersConfig',
'mhackspace.base',
'mhackspace.subscriptions', 'mhackspace.subscriptions',
'mhackspace.feeds', 'mhackspace.feeds',
'mhackspace.contact', 'mhackspace.contact',
@ -189,6 +190,7 @@ STATICFILES_DIRS = (
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'sass_processor.finders.CssFinder',
) )
# MEDIA CONFIGURATION # MEDIA CONFIGURATION
@ -252,7 +254,7 @@ LOGIN_URL = 'account_login'
AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify'
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
INSTALLED_APPS += ("compressor", ) INSTALLED_APPS += ("compressor", 'sass_processor',)
STATICFILES_FINDERS += ("compressor.finders.CompressorFinder", ) STATICFILES_FINDERS += ("compressor.finders.CompressorFinder", )
# Location of root django.contrib.admin URL, use {% url 'admin:index' %} # Location of root django.contrib.admin URL, use {% url 'admin:index' %}
@ -262,7 +264,7 @@ ADMIN_URL = '^admin/'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
payment_providers = { PAYMENT_PROVIDERS = {
'braintree': { 'braintree': {
'mode': 'sandbox', 'mode': 'sandbox',
'credentials': { 'credentials': {
@ -275,7 +277,7 @@ payment_providers = {
"mode": "sandbox", # sandbox or live "mode": "sandbox", # sandbox or live
'credentials': { 'credentials': {
"mode": "sandbox", # sandbox or live "mode": "sandbox", # sandbox or live
"client_id": end('PAYPAL_CLIENT_ID'), "client_id": env('PAYPAL_CLIENT_ID'),
"client_secret": env('PAYPAL_CLIENT_SECRET')} "client_secret": env('PAYPAL_CLIENT_SECRET')}
}, },
'gocardless':{ 'gocardless':{

View File

@ -56,6 +56,9 @@ X_FRAME_OPTIONS = 'DENY'
# Hosts/domain names that are valid for this site # Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['maidstone-hackspace.org.uk']) ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['maidstone-hackspace.org.uk'])
ALLOWED_HOSTS.append('172.*')
ALLOWED_HOSTS.append('172.18.0.5')
# END SITE CONFIGURATION # END SITE CONFIGURATION
INSTALLED_APPS += ('gunicorn', ) INSTALLED_APPS += ('gunicorn', )

View File

@ -1,8 +1,20 @@
version: '2' version: '2'
volumes: volumes:
postgres_data: {} gunicorn_socket:
postgres_backup: {} driver: local
postgres_data:
driver: local
postgres_backup:
driver: local
# volumes:
# sockets:
# driver: local
# data:
# driver: local
services: services:
postgres: postgres:
@ -22,37 +34,41 @@ services:
- redis - redis
command: /gunicorn.sh command: /gunicorn.sh
env_file: .env env_file: .env
volumes:
- .:/app
- gunicorn_socket:/data/sockets
nginx: nginx:
build: ./compose/nginx build: ./compose/nginx
env_file: .env
depends_on: depends_on:
- django - django
- certbot # - certbot
ports:
- "0.0.0.0:80:80"
environment: environment:
- MY_DOMAIN_NAME=maidstone-hackspace.org.uk - MY_DOMAIN_NAME=maidstone-hackspace.org.uk
ports: ports:
- "0.0.0.0:80:80" - "0.0.0.0:80:80"
- "0.0.0.0:443:443"
volumes: volumes:
- /etc/letsencrypt:/etc/letsencrypt - .:/app
- /var/lib/letsencrypt:/var/lib/letsencrypt - gunicorn_socket:/data/sockets
# - "0.0.0.0:443:443"
# volumes:
# - /etc/letsencrypt:/etc/letsencrypt
# - /var/lib/letsencrypt:/var/lib/letsencrypt
certbot: # certbot:
image: quay.io/letsencrypt/letsencrypt # image: quay.io/letsencrypt/letsencrypt
command: bash -c "sleep 6 && certbot certonly -n --standalone -d maidstone-hackspace.org.uk --text --agree-tos --email support@maidstone-hackspace.org.uk --server https://acme-v01.api.letsencrypt.org/directory --rsa-key-size 4096 --verbose --keep-until-expiring --standalone-supported-challenges http-01" # command: bash -c "sleep 6 && certbot certonly -n --standalone -d maidstone-hackspace.org.uk --text --agree-tos --email support@maidstone-hackspace.org.uk --server https://acme-v01.api.letsencrypt.org/directory --rsa-key-size 4096 --verbose --keep-until-expiring --standalone-supported-challenges http-01"
entrypoint: "" # entrypoint: ""
volumes: # volumes:
- /etc/letsencrypt:/etc/letsencrypt # - /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt # - /var/lib/letsencrypt:/var/lib/letsencrypt
ports: # ports:
- "80" # - "80"
- "443" # - "443"
environment: # environment:
- TERM=xterm # - TERM=xterm
redis: redis:

View File

@ -29,16 +29,16 @@ DJANGO_ACCOUNT_ALLOW_REGISTRATION=True
COMPRESS_ENABLED= COMPRESS_ENABLED=
PAYMENT_ENVIRONMENT = 'sandbox' PAYMENT_ENVIRONMENT=sandbox
BRAINTREE_MERCHANT_ID = '' BRAINTREE_MERCHANT_ID=demo
BRAINTREE_PUBLIC_KEY = '' BRAINTREE_PUBLIC_KEY=demo
BRAINTREE_PRIVATE_KEY = '' BRAINTREE_PRIVATE_KEY=demo
PAYPAL_CLIENT_ID = "" PAYPAL_CLIENT_ID=demo
PAYPAL_CLIENT_SECRET = "" PAYPAL_CLIENT_SECRET=demo
GOCARDLESS_APP_ID = '' GOCARDLESS_APP_ID=demo
GOCARDLESS_APP_SECRET = '' GOCARDLESS_APP_SECRET=demo
GOCARDLESS_ACCESS_TOKEN = '' GOCARDLESS_ACCESS_TOKEN=demo
GOCARDLESS_MERCHANT_ID = '' GOCARDLESS_MERCHANT_ID=demo

55
live.yml Normal file
View File

@ -0,0 +1,55 @@
version: '2'
volumes:
gunicorn_socket: {}
postgres_data_dev: {}
postgres_backup_dev: {}
services:
postgres:
build: ./compose/postgres
volumes:
- postgres_data_dev:/var/lib/postgresql/data
- postgres_backup_dev:/backups
# environment:
# - POSTGRES_USER=${POSTGRES_USER}
env_file: .env
django:
build:
context: .
dockerfile: ./compose/django/Dockerfile
command: /start-dev.sh
depends_on:
- postgres
environment:
- POSTGRES_USER=${POSTGRES_USER}
- USE_DOCKER=yes
volumes:
- .:/app
- gunicorn_socket:/var/run/gunicorn/
ports:
- "8180:8000"
links:
- postgres
- mailhog
nginx:
build: ./compose/nginx
depends_on:
- django
environment:
- MY_DOMAIN_NAME=maidstone-hackspace.org.uk
ports:
- "0.0.0.0:80:80"
# - "0.0.0.0:443:443"
volumes:
- gunicorn_socket:/var/run/gunicorn/
mailhog:
image: mailhog/mailhog
ports:
- "8125:8025"
redis:
image: redis:latest

View File

@ -0,0 +1,19 @@
from autofixture import AutoFixture
from django.core.management.base import BaseCommand
from mhackspace.feeds.models import Article
from mhackspace.users.models import User
class Command(BaseCommand):
help = 'Imports the RSS feeds from active blogs'
def handle(self, *args, **options):
users = AutoFixture(User)
users.create(10)
feeds = AutoFixture(User)
feeds.create(10)
self.stdout.write(
self.style.SUCCESS(
'Finished creating test data'))

View File

View File

@ -1,8 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 14:04 # Generated by Django 1.10.5 on 2017-01-28 18:38
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -13,14 +17,35 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('title', models.CharField(max_length=255)),
('original_image', models.URLField(blank=True, max_length=255, null=True)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('description', models.TextField()),
('displayed', models.BooleanField(default=True)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Feed', name='Feed',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.CharField(max_length=255)), ('home_url', models.URLField(verbose_name='Site Home Page')),
('feed_url', models.URLField(verbose_name='RSS Feed URL')),
('title', models.CharField(max_length=255)),
('author', models.CharField(max_length=255)), ('author', models.CharField(max_length=255)),
('tags', models.CharField(max_length=255)), ('tags', models.CharField(blank=True, max_length=255)),
('image', models.ImageField(upload_to='')), ('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('enabled', models.BooleanField(default=True)),
], ],
), ),
migrations.AddField(
model_name='article',
name='feed',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed'),
),
] ]

View File

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 20:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='id',
field=models.IntegerField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='feed',
name='image',
field=models.ImageField(blank=True, upload_to=''),
),
migrations.AlterField(
model_name='feed',
name='tags',
field=models.CharField(blank=True, max_length=255),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 20:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0002_auto_20170104_2033'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='id',
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 21:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0003_auto_20170104_2035'),
]
operations = [
migrations.AddField(
model_name='feed',
name='enabled',
field=models.BooleanField(default=True),
),
]

View File

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 03:42
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration):
dependencies = [
('feeds', '0004_feed_enabled'),
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('title', models.CharField(max_length=255)),
('original_image', models.URLField(blank=True, max_length=255, null=True)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('description', models.TextField()),
('displayed', models.BooleanField(default=True)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.RemoveField(
model_name='feed',
name='url',
),
migrations.AddField(
model_name='feed',
name='feed_url',
field=models.URLField(default='http://thearduinoguy.org/?feed=rss2', verbose_name='RSS Feed URL'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='home_url',
field=models.URLField(default='http://thearduinoguy.org/', verbose_name='Site Home Page'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='title',
field=models.CharField(default='The Arduino Guy', max_length=255),
preserve_default=False,
),
migrations.AlterField(
model_name='feed',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='feed',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title')),
),
migrations.AddField(
model_name='article',
name='feed',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed'),
),
]

View File

@ -10,11 +10,11 @@ from mhackspace.users.models import User
class MemberListView(LoginRequiredMixin, ListView): class MemberListView(LoginRequiredMixin, ListView):
template_name = 'pages/members.html' template_name = 'pages/members.html'
queryset = User.objects.prefetch_related('users', 'groups') queryset = User.objects.prefetch_related('user', 'groups')
paginate_by = 10 paginate_by = 10
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(MemberListView, self).get_context_data(**kwargs) context = super(MemberListView, self).get_context_data(**kwargs)
context['members'] = self.get_queryset() context['members'] = self.get_queryset()
context['total'] = self.get_queryset().filter(groups__name='member').count() context['total'] = self.get_queryset().filter(groups__name='members').count()
return context return context

View File

View File

@ -0,0 +1,45 @@
from datetime import datetime
from django.utils import timezone
from django.contrib.auth.models import Group
from django.forms.models import model_to_dict
from django.core.management.base import BaseCommand
from mhackspace.subscriptions.payments import select_provider
from mhackspace.users.models import Membership, User
from mhackspace.subscriptions.models import Payments
class Command(BaseCommand):
help = 'Update user subscriptions'
def handle(self, *args, **options):
provider = select_provider('gocardless')
self.stdout.write(
self.style.NOTICE(
'== Gocardless customer payments =='))
Payments.objects.all().delete()
payment_objects = []
for customer in provider.fetch_customers():
# self.stdout.write(str(dir(customer)))
# self.stdout.write(str(customer))
payment_objects.append(Payments(
user=None,
user_reference=customer.get('user_id'),
user_email=customer.get('email'),
reference=customer.get('payment_id'),
amount=customer.get('amount'),
type=Payments.lookup_payment_type(customer.get('payment_type')),
date=customer.get('payment_date')
))
# self.stdout.write(str(customer.email))
# self.stdout.write(str(dir(customer['email']())))
self.stdout.write(
self.style.SUCCESS(
'\t{reference} - {amount} - {type} - {user_email}'.format(**model_to_dict(payment_objects[-1]))))
Payments.objects.bulk_create(payment_objects)

View File

@ -24,4 +24,4 @@ class Command(BaseCommand):
for sub in provider.fetch_subscriptions(): for sub in provider.fetch_subscriptions():
self.stdout.write( self.stdout.write(
self.style.SUCCESS( self.style.SUCCESS(
'\t{reference} - {amount} - {status} - {email}'.format(**sub))) '\t{start_date} {reference} - {amount} - {status} - {email}'.format(**sub)))

View File

@ -0,0 +1,51 @@
from datetime import datetime
from django.utils import timezone
from django.contrib.auth.models import Group
from django.forms.models import model_to_dict
from django.core.management.base import BaseCommand
from mhackspace.subscriptions.payments import select_provider
from mhackspace.users.models import Membership, User
class Command(BaseCommand):
help = 'Update user subscriptions'
def handle(self, *args, **options):
provider = select_provider('gocardless')
self.stdout.write(
self.style.NOTICE(
'== Gocardless subscriptions =='))
Membership.objects.all().delete()
subscriptions = []
group = Group.objects.get(name='members')
for sub in provider.fetch_subscriptions():
try:
user_model = User.objects.get(email=sub.get('email'))
if sub.get('status') == 'active':
user_model.groups.add(group)
except User.DoesNotExist:
user_model = None
self.stdout.write(sub.get('status'))
subscriptions.append(
Membership(
user=user_model,
email=sub.get('email'),
reference=sub.get('reference'),
payment=10.00,
date= sub.get('start_date'),
# date=timezone.now(),
status=Membership.lookup_status(name=sub.get('status'))
)
)
self.stdout.write(
self.style.SUCCESS(
'\t{reference} - {payment} - {status} - {email}'.format(**model_to_dict(subscriptions[-1]))))
Membership.objects.bulk_create(subscriptions)

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-29 21:55
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Payments',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_reference', models.CharField(max_length=255)),
('user_email', models.CharField(max_length=255)),
('reference', models.CharField(max_length=255, unique=True)),
('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=6)),
('type', models.PositiveSmallIntegerField(default=0)),
('date', models.DateTimeField()),
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='from_user', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from stdimage.models import StdImageField
PAYMENT_TYPES = {
'unknown': 0,
'subscription': 1,
'payment': 2
}
@python_2_unicode_compatible
class Payments(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
default=None,
related_name='from_user'
)
user_reference = models.CharField(max_length=255)
user_email = models.CharField(max_length=255)
reference = models.CharField(max_length=255, unique=True)
amount = models.DecimalField(max_digits=6, decimal_places=2, default=0.0)
type = models.PositiveSmallIntegerField(default=0)
date = models.DateTimeField()
def lookup_payment_type(name):
return PAYMENT_TYPES.get(name, 0)
def get_payment_type(self):
return self.type
def __str__(self):
return self.reference

View File

@ -3,7 +3,8 @@ import pytz
import gocardless import gocardless
import braintree import braintree
from django.conf.settings import payment_providers from django.conf import settings
payment_providers = settings.PAYMENT_PROVIDERS
# import gocardless_pro # import gocardless_pro
# import paypalrestsdk as paypal # import paypalrestsdk as paypal
@ -45,18 +46,48 @@ class gocardless_provider:
return { return {
'amount': subscription.amount, 'amount': subscription.amount,
'start_date': subscription.created_at, 'start_date': subscription.created_at,
'reference': subscription.id 'reference': subscription.id,
'success': response.success
} }
def fetch_customers(self):
merchant = gocardless.client.merchant()
for customer in merchant.bills():
user = customer.user()
# print(dir(customer))
# print(dir(customer.reference_fields))
# print(customer.reference_fields)
# print(customer.payout_id)
# print(customer.reference_fields.payout_id)
result = {
'user_id': user.id,
'email': user.email,
'status': customer.status,
'payment_id': customer.id,
'payment_type': customer.source_type,
'payment_date': customer.created_at,
'amount': customer.amount
}
yield result #customer
# for customer in self.client.users():
# result = {
# 'email': customer.email,
# 'created_date': customer.created_at,
# 'first_name': customer.first_name,
# 'last_name': customer.last_name
# }
# yield customer
def fetch_subscriptions(self): def fetch_subscriptions(self):
for paying_member in self.client.subscriptions(): for paying_member in self.client.subscriptions():
user=paying_member.user() user=paying_member.user()
# for bill in paying_member.bills():
# print('test') #gocardless does not have a reference so we use the id instead
# print(dir(bill))
# print(bill.created_at)
# print(dir(paying_member))
# print(paying_member.reference_fields)
yield { yield {
'status': paying_member.status, 'status': paying_member.status,
'email': user.email, 'email': user.email,
@ -70,6 +101,16 @@ class gocardless_provider:
def get_token(self): def get_token(self):
return 'N/A' return 'N/A'
def cancel_subscribe(self, reference):
subscription = gocardless.client.subscription(reference)
response = subscription.cancel()
return {
'amount': subscription.amount,
'start_date': subscription.created_at,
'reference': subscription.id,
'success': response.success
}
def create_subscription(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'): def create_subscription(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'):
return gocardless.client.new_subscription_url( return gocardless.client.new_subscription_url(
amount=float(amount), amount=float(amount),
@ -199,40 +240,6 @@ class payment:
'reference': paying_member.id, 'reference': paying_member.id,
'amount': paying_member.amount} 'amount': paying_member.amount}
if self.provider == 'paypal':
#~ I-S39170DK26AF
#~ start_date, end_date = "2014-07-01", "2014-07-20"
billing_agreement = paypal.BillingAgreement.find('')
print(billing_agreement)
print(dir(billing_agreement))
#~ print billing_agreement.search_transactions(start_date, end_date)
#~ transactions = billing_agreement.search_transactions(start_date, end_date)
payment_history = paypal.Payment.all({"count": 2})
# List Payments
print("List Payment:")
print(payment_history)
for payment in payment_history.payments:
print(" -> Payment[%s]" % (payment.id))
#~ print paypal.BillingAgreement.all()
history = paypal.BillingPlan.all(
{"status": "CREATED", "page_size": 5, "page": 1, "total_required": "yes"})
print(history)
print("List BillingPlan:")
for plan in history.plans:
print(dir(plan))
print(plan.to_dict())
print(" -> BillingPlan[%s]" % (plan.id))
#~ merchant = gocardless.client.merchant()
#~ for paying_member in merchant.subscriptions():
#~ user=paying_member.user()
#~ yield {
#~ 'email': user.email,
#~ 'start_date': paying_member.created_at,
#~ 'reference': paying_member.id,
#~ 'amount': paying_member.amount}
def subscribe_confirm(self, args): def subscribe_confirm(self, args):
if self.provider == 'gocardless': if self.provider == 'gocardless':
@ -363,8 +370,6 @@ class payment:
print('---------------------') print('---------------------')
print(args) print(args)
from pprint import pprint from pprint import pprint
if self.provider == 'paypal': if self.provider == 'paypal':
print(args.get('paymentId')) print(args.get('paymentId'))

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from test_plus.test import TestCase from test_plus.test import TestCase
# import unittest from unittest import skip
from mock import patch, Mock from mock import patch, Mock
from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider
@ -26,31 +26,73 @@ class TestPaymentGatewaysGocardless(TestCase):
self.provider = gocardless_provider() self.provider = gocardless_provider()
return self.provider #self.provider return self.provider #self.provider
def test_confirm_subscription_callback(self): @skip("Need to implement")
with patch('gocardless.client.confirm_resources') as mock_subscription: @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
self.provider = gocardless_provider() def test_unsubscribe(self, mock_subscription):
mock_subscription.return_value = Mock(success='success')
def test_fetch_subscription_gocardless(self): mock_subscription.cancel.return_value = Mock(
items = [Mock(
id='01', id='01',
status='active', status='active',
amount=20.00, amount=20.00,
reference='ref01',
created_at='date' created_at='date'
)] )
items[-1].user.return_value = Mock(email='test@test.com') result = self.provider.cancel_subscription(reference='M01')
self.assertEqual(result.get('amount'), 20.00)
self.assertEqual(result.get('start_date'), 'date')
self.assertEqual(result.get('reference'), '01')
self.assertEqual(result.get('success'), 'success')
# @patch('mhackspace.subscriptions.payments.gocardless.request.requests.get', autospec=True)
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
@patch('mhackspace.subscriptions.payments.gocardless.client.confirm_resource', autospec=True)
def test_confirm_subscription_callback(self, mock_confirm, mock_subscription):
mock_confirm.return_value = Mock(success='success')
mock_subscription.return_value = Mock(
id='01',
status='active',
amount=20.00,
created_at='date'
)
request_params = {
'resource_uri': 'http://gocardless/resource/url/01',
'resource_id': '01',
'resource_type': 'subscription',
'signature': 'sig',
'state': 'inactive'
}
result = self.provider.subscribe_confirm(request_params)
self.assertEqual(result.get('amount'), 20.00)
self.assertEqual(result.get('start_date'), 'date')
self.assertEqual(result.get('reference'), '01')
self.assertEqual(result.get('success'), 'success')
def test_fetch_subscription_gocardless(self):
item = Mock(
id='01',
status='active',
amount=20.00,
created_at='date'
)
item.user.return_value = Mock(email='test@test.com')
self.provider.client = Mock() self.provider.client = Mock()
self.provider.client.subscriptions = Mock(return_value=items) self.provider.client.subscriptions = Mock(return_value=[item])
# mock out gocardless subscriptions method, and return our own values
for item in self.provider.fetch_subscriptions(): for item in self.provider.fetch_subscriptions():
self.assertEqual(item.get('status'), 'active') self.assertEqual(item.get('status'), 'active')
self.assertEqual(item.get('email'), 'test@test.com') self.assertEqual(item.get('email'), 'test@test.com')
self.assertEqual(item.get('reference'), 'ref01') self.assertEqual(item.get('reference'), '01')
self.assertEqual(item.get('start_date'), 'date') self.assertEqual(item.get('start_date'), 'date')
self.assertEqual(item.get('amount'), 20.00) self.assertEqual(item.get('amount'), 20.00)
class TestPaymentGatewaysBraintree(TestCase): class DisabledestPaymentGatewaysBraintree(TestCase):
@patch('mhackspace.subscriptions.payments.braintree.Configuration.configure') @patch('mhackspace.subscriptions.payments.braintree.Configuration.configure')
def auth_braintree(self, mock_request): def auth_braintree(self, mock_request):
# mock braintree initalisation request # mock braintree initalisation request

View File

@ -91,8 +91,25 @@
</nav> </nav>
</div> </div>
<div class="container"> <ul class="nav flex-column navbar-left" style="position:absolute;top:80px;background-color:#bbbbbb;">
<li class="nav-item">
<a class="nav-link active" href="#">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/members">Members</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Mailing List</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="/equipment">Equipment</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="/accounts/signout">Sign out</a>
</li>
</ul>
<div class="container">
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">{{ message }}</div> <div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">{{ message }}</div>

View File

@ -4,5 +4,7 @@
{% block content %} {% block content %}
<h2>Introduction</h2> <h2>Introduction</h2>
Hackspaces are a shared space where artists, designers, makers, hackers, programmers, tinkerers, professionals and hobbyists can work on their projects, share knowledge and collaborate.We are in the process of developing Maidstone Hackspace. We're previous members of (ICMP) and looking to form a new space in the future. At the moment, communication is via google groups, email, and the website. If you're at all intrested please join our mailing list and make yourself known! Hackspaces are a shared space where artists, designers, makers, hackers, programmers, tinkerers, professionals and hobbyists can work on their projects, share knowledge and collaborate.We are in the process of developing Maidstone Hackspace. We're previous members of (ICMP) and looking to form a new space in the future. At the moment, communication is via google groups, email, and the website. If you're at all intrested please join our mailing list and make yourself known!
Click here to chat with us https://hangouts.google.com/group/oDcAL0nDfQYfO3qq1
{% show_feeds %} {% show_feeds %}
{% endblock content %} {% endblock content %}

View File

@ -4,8 +4,8 @@
Members feed Members feed
<div class="row"> <div class="row">
<div class="">Users {{ paginator.count }}</div> <div class="col">Users {{ paginator.count }}</div>
<div class="">Members {{ total }}</div> <div class="col">Members {{ total }}</div>
</div> </div>
<div class="card-deck-wrapper"> <div class="card-deck-wrapper">
@ -17,13 +17,14 @@ Members feed
<h4 class="card-title">{{ member.name }}</h4> <h4 class="card-title">{{ member.name }}</h4>
<p class="card-text">{{ member.users.description }}</p> <p class="card-text">{{ member.users.description }}</p>
<p class="card-text">{{ member.users.skills }}</p> <p class="card-text">{{ member.users.skills }}</p>
<p class="card-text">{{ member.status }}</p>
{% for group in member.groups.all %} {% for group in member.groups.all %}
{{ group.name }} {{ group.name }}
{% endfor %} {% endfor %}
</div> </div>
<div class="card-block"> <div class="card-block">
<p class="card-text">{{ member.userblurb }}</p> <p class="card-text">{{ member.blurb }}</p>
</div> </div>
<div class="card-block"> <div class="card-block">
<a href="{{ feed.url }}" class="card-link">View Original</a> <a href="{{ feed.url }}" class="card-link">View Original</a>

View File

@ -5,12 +5,25 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<style>
#membercard {
color: #000;
background-color: #8C50A5;
border-radius: 14px;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.5);
background-image: url('/static/images/membership_card_background.png');
width: 430px;
height: 240px;
margin-left: 20px;
text-shadow: 1px 1px #FFF;
position: relative;
float: right;
}
</style>
<h2>Profile</h2> <h2>Profile</h2>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<img src="{{ user.image.profile.url }}" alt="Profile image"/> <img src="{{ user.image.profile.url }}" alt="Profile image"/>
<p>{{ user.username }}</p> <p>{{ user.username }}</p>
<p>{{ user.name }}</p> <p>{{ user.name }}</p>
<p>{{ user.email }}</p> <p>{{ user.email }}</p>
@ -18,13 +31,18 @@
<p>Member since</p> <p>Member since</p>
<p>Description: {{ blurb.description }}</p> <p>Description: {{ blurb.description }}</p>
<p>Skills: {{ blurb.description }}</p> <p>Skills: {{ blurb.description }}</p>
<h3>Membership status</h3>
<p>Membership Status: {{ membership.get_status }}</p>
<p>Last Payment: {{membership.date}}</p>
<p>Amount: &pound;{{membership.payment}}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div id="membercard" class="registered"> <div id="membercard" class="registered">
<div class="date">Joined </div> <div class="date">Joined {{membership.date}}</div>
<div class="container"> <div class="container">
<div class="middle"> <div class="middle">
<p>MHS{{ user.id|stringformat:"05d" }}</p><p>Change me</p> <p>MHS{{ user.id|stringformat:"05d" }}</p><p>{{user.name}}{{user.last_name}}</p>
<a href="/profile/membership/cancel">Cancel Membership</a> <a href="/profile/membership/cancel">Cancel Membership</a>
</div> </div>
</div> </div>

View File

@ -0,0 +1 @@
[{"model": "auth.group", "pk": 1, "fields": {"name": "members", "permissions": []}}]

View File

@ -2,9 +2,9 @@
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from .models import UserBlurb from .models import Blurb
class UserBlurbForm(ModelForm): class BlurbForm(ModelForm):
class Meta: class Meta:
model = UserBlurb model = Blurb
exclude = ['user'] exclude = ['user']

View File

@ -1,11 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-23 04:36 # Generated by Django 1.10.5 on 2017-01-28 19:46
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import stdimage.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -32,16 +35,38 @@ class Migration(migrations.Migration):
('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')), ('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')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')), ('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to='avatars/')),
('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')), ('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')), ('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={ options={
'verbose_name_plural': 'users',
'verbose_name': 'user', 'verbose_name': 'user',
'abstract': False, 'abstract': False,
'verbose_name_plural': 'users',
}, },
managers=[ managers=[
('objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.CreateModel(
name='Blurb',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('skills', models.CharField(max_length=255)),
('description', models.TextField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Membership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment', models.DecimalField(decimal_places=2, default=0.0, max_digits=6)),
('date', models.DateTimeField()),
('reference', models.CharField(max_length=255)),
('status', models.PositiveSmallIntegerField(default=0)),
('email', models.CharField(max_length=255)),
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)),
],
),
] ]

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 08:53
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Membership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment', models.DecimalField(decimal_places=2, max_digits=6)),
('date', models.DateTimeField()),
('reference', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserBlurb',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('skills', models.CharField(max_length=255)),
('description', models.TextField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 19:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_membership_userblurb'),
]
operations = [
migrations.AddField(
model_name='user',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='/upload/avatars'),
),
]

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 20:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_user_image'),
]
operations = [
migrations.RenameModel(
old_name='Membership',
new_name='UserMembership',
),
migrations.AlterField(
model_name='user',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='avatars/'),
),
]

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 00:50
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20170106_2030'),
]
operations = [
migrations.AlterField(
model_name='user',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to='avatars/'),
),
migrations.AlterField(
model_name='userblurb',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -29,14 +29,44 @@ class User(AbstractUser):
return reverse('users:detail', kwargs={'username': self.username}) return reverse('users:detail', kwargs={'username': self.username})
class UserBlurb(models.Model): class Blurb(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='users') user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='+')
skills = models.CharField(max_length=255) skills = models.CharField(max_length=255)
description = models.TextField() description = models.TextField()
class UserMembership(models.Model): MEMBERSHIP_STRING = {
user = models.ForeignKey(settings.AUTH_USER_MODEL) 0: 'Guest user',
payment = models.DecimalField(max_digits=6, decimal_places=2) 1: 'Active membership',
3: 'Membership Expired'
}
MEMBERSHIP_STATUS = {
'active': 1,
'cancelled': 2
}
class Membership(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
default=None,
related_name='user'
)
payment = models.DecimalField(max_digits=6, decimal_places=2, default=0.0)
date = models.DateTimeField() date = models.DateTimeField()
reference = models.CharField(max_length=255) reference = models.CharField(max_length=255)
status = models.PositiveSmallIntegerField(default=0)
email = models.CharField(max_length=255)
@property
def get_status(self):
return MEMBERSHIP_STRING[self.status]
def lookup_status(name):
if not name:
return 0
return MEMBERSHIP_STATUS.get(name.lower(), 0)
def __str__(self):
return self.reference

View File

@ -7,10 +7,10 @@ from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from .models import User from .models import User
from .models import UserBlurb from .models import Blurb
from .models import UserMembership from .models import Membership
from .forms import UserBlurbForm from .forms import BlurbForm
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(LoginRequiredMixin, DetailView):
model = User model = User
@ -21,8 +21,8 @@ class UserDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects # xxx will be available in the template as the related objects
context = super(UserDetailView, self).get_context_data(**kwargs) context = super(UserDetailView, self).get_context_data(**kwargs)
context['blurb'] = UserBlurb.objects.filter(user=self.get_object()).first() context['blurb'] = Blurb.objects.filter(user=self.get_object()).first()
context['membership'] = UserMembership.objects.filter(user=self.get_object()).first() context['membership'] = Membership.objects.filter(user=self.get_object()).first()
return context return context
class UserRedirectView(LoginRequiredMixin, RedirectView): class UserRedirectView(LoginRequiredMixin, RedirectView):
@ -45,8 +45,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(UserUpdateView, self).get_context_data(**kwargs) context = super(UserUpdateView, self).get_context_data(**kwargs)
profile = UserBlurb.objects.filter(user=self.get_object()).first() profile = Blurb.objects.filter(user=self.get_object()).first()
context['form_blurb'] = UserBlurbForm(instance=profile) context['form_blurb'] = BlurbForm(instance=profile)
return context return context
def get_object(self): def get_object(self):
@ -54,8 +54,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return User.objects.get(username=self.request.user.username) return User.objects.get(username=self.request.user.username)
def form_valid(self, form): def form_valid(self, form):
profile = UserBlurb.objects.filter(user=self.get_object()).first() profile = Blurb.objects.filter(user=self.get_object()).first()
form_blurb = UserBlurbForm(self.request.POST, instance=profile) form_blurb = BlurbForm(self.request.POST, instance=profile)
if form_blurb.is_valid(): if form_blurb.is_valid():
blurb_model = form_blurb.save(commit=False) blurb_model = form_blurb.save(commit=False)
blurb_model.user = self.request.user blurb_model.user = self.request.user

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
# This file is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
-r requirements/production.txt

View File

@ -42,16 +42,19 @@ django-redis==4.7.0
redis>=2.10.5 redis>=2.10.5
rcssmin==1.0.6 rcssmin==1.0.6
django-compressor==2.1 django-compressor==2.1
django-sass-processor==0.5.3
lxml==3.7.2 lxml==3.7.2
# Your custom requirements go here
mock==2.0.0 mock==2.0.0
gocardless gocardless
braintree braintree==3.34.0
# Your custom requirements go here django-autofixture==0.12.1
-e git+https://github.com/olymk2/scaffold.git#egg=scaffold
git+https://github.com/olymk2/scaffold.git

0
staticfiles/staticfiles Normal file
View File