diff --git a/.drone.yml b/.drone.yml index 3075c22..fb192f6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,7 +6,12 @@ pipeline: - USE_DOCKER=yes - DJANGO_SETTINGS_MODULE=config.settings.test commands: - - python manage.py test mhackspace.subscriptions --verbosity 2 + - cp -n env.example .env + - python manage.py test mhackspace --verbosity 2 + + deploy: + + #volumes: # postgres_data_dev: {} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..216b870 --- /dev/null +++ b/.travis.yml @@ -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" diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..2d2ce88 --- /dev/null +++ b/circle.yml @@ -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 diff --git a/compose/django/Dockerfile b/compose/django/Dockerfile index a198604..b26d2fd 100644 --- a/compose/django/Dockerfile +++ b/compose/django/Dockerfile @@ -11,6 +11,7 @@ RUN pip install -r /requirements/production.txt \ COPY . /app RUN chown -R django /app +RUN mkdir -p /data/sockets COPY ./compose/django/gunicorn.sh /gunicorn.sh COPY ./compose/django/entrypoint.sh /entrypoint.sh @@ -19,7 +20,8 @@ RUN sed -i 's/\r//' /entrypoint.sh \ && chmod +x /entrypoint.sh \ && chown django /entrypoint.sh \ && chmod +x /gunicorn.sh \ - && chown django /gunicorn.sh + && chown django /gunicorn.sh \ + && chown django /data/sockets WORKDIR /app diff --git a/compose/django/entrypoint.sh b/compose/django/entrypoint.sh index eb70a65..158fe37 100644 --- a/compose/django/entrypoint.sh +++ b/compose/django/entrypoint.sh @@ -33,5 +33,4 @@ until postgres_ready; do sleep 1 done ->&2 echo "Postgres is up - continuing..." exec $cmd diff --git a/compose/django/gunicorn.sh b/compose/django/gunicorn.sh index 014f173..52c50ca 100644 --- a/compose/django/gunicorn.sh +++ b/compose/django/gunicorn.sh @@ -1,3 +1,6 @@ #!/bin/sh python /app/manage.py collectstatic --noinput -/usr/local/bin/gunicorn config.wsgi -w 4 -b 0.0.0.0:5000 --chdir=/app \ No newline at end of file +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 diff --git a/compose/nginx/Dockerfile b/compose/nginx/Dockerfile index 5984825..136fd5d 100644 --- a/compose/nginx/Dockerfile +++ b/compose/nginx/Dockerfile @@ -4,6 +4,6 @@ ADD nginx.conf /etc/nginx/nginx.conf ADD start.sh /start.sh 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 diff --git a/compose/nginx/nginx.conf b/compose/nginx/nginx.conf index 3b9d2a3..a200939 100644 --- a/compose/nginx/nginx.conf +++ b/compose/nginx/nginx.conf @@ -26,7 +26,8 @@ http { #gzip on; upstream app { - server django:5000; + #server django:5000; + server unix:/data/sockets/gunicorn.sock; } server { @@ -36,12 +37,12 @@ http { server_name ___my.example.com___ ; - location /.well-known/acme-challenge { - proxy_pass http://certbot:80; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto https; - } +# location /.well-known/acme-challenge { +# proxy_pass http://certbot:80; +# proxy_set_header Host $host; +# proxy_set_header X-Forwarded-For $remote_addr; +# proxy_set_header X-Forwarded-Proto https; +# } location / { diff --git a/config/settings/common.py b/config/settings/common.py index 5dd17db..fc4941c 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -16,7 +16,7 @@ ROOT_DIR = environ.Path(__file__) - 3 # (mhackspace/config/settings/common.py - APPS_DIR = ROOT_DIR.path('mhackspace') env = environ.Env() -env.read_env() +env.read_env('%s/.env' % ROOT_DIR) # APP CONFIGURATION # ------------------------------------------------------------------------------ @@ -48,6 +48,7 @@ LOCAL_APPS = ( # custom users app # Your stuff: custom apps go here 'mhackspace.users.apps.UsersConfig', + 'mhackspace.base', 'mhackspace.subscriptions', 'mhackspace.feeds', 'mhackspace.contact', @@ -189,6 +190,7 @@ STATICFILES_DIRS = ( STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'sass_processor.finders.CssFinder', ) # MEDIA CONFIGURATION @@ -252,7 +254,7 @@ LOGIN_URL = 'account_login' AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' # django-compressor # ------------------------------------------------------------------------------ -INSTALLED_APPS += ("compressor", ) +INSTALLED_APPS += ("compressor", 'sass_processor',) STATICFILES_FINDERS += ("compressor.finders.CompressorFinder", ) # Location of root django.contrib.admin URL, use {% url 'admin:index' %} @@ -262,7 +264,7 @@ ADMIN_URL = '^admin/' # ------------------------------------------------------------------------------ -payment_providers = { +PAYMENT_PROVIDERS = { 'braintree': { 'mode': 'sandbox', 'credentials': { @@ -275,7 +277,7 @@ payment_providers = { "mode": "sandbox", # sandbox or live 'credentials': { "mode": "sandbox", # sandbox or live - "client_id": end('PAYPAL_CLIENT_ID'), + "client_id": env('PAYPAL_CLIENT_ID'), "client_secret": env('PAYPAL_CLIENT_SECRET')} }, 'gocardless':{ diff --git a/config/settings/production.py b/config/settings/production.py index e8d8d20..5ab3a21 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -56,6 +56,9 @@ X_FRAME_OPTIONS = 'DENY' # Hosts/domain names that are valid for this site # 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.append('172.*') +ALLOWED_HOSTS.append('172.18.0.5') + # END SITE CONFIGURATION INSTALLED_APPS += ('gunicorn', ) diff --git a/docker-compose.yml b/docker-compose.yml index 8f0abc6..99d65fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,20 @@ version: '2' volumes: - postgres_data: {} - postgres_backup: {} + gunicorn_socket: + driver: local + postgres_data: + driver: local + postgres_backup: + driver: local + + +# volumes: +# sockets: +# driver: local +# data: +# driver: local + services: postgres: @@ -22,37 +34,41 @@ services: - redis command: /gunicorn.sh env_file: .env + volumes: + - .:/app + - gunicorn_socket:/data/sockets nginx: build: ./compose/nginx + env_file: .env depends_on: - django - - certbot - - ports: - - "0.0.0.0:80:80" +# - certbot environment: - MY_DOMAIN_NAME=maidstone-hackspace.org.uk ports: - "0.0.0.0:80:80" - - "0.0.0.0:443:443" volumes: - - /etc/letsencrypt:/etc/letsencrypt - - /var/lib/letsencrypt:/var/lib/letsencrypt + - .:/app + - gunicorn_socket:/data/sockets +# - "0.0.0.0:443:443" +# volumes: +# - /etc/letsencrypt:/etc/letsencrypt +# - /var/lib/letsencrypt:/var/lib/letsencrypt - certbot: - 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" - entrypoint: "" - volumes: - - /etc/letsencrypt:/etc/letsencrypt - - /var/lib/letsencrypt:/var/lib/letsencrypt - ports: - - "80" - - "443" - environment: - - TERM=xterm +# certbot: +# 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" +# entrypoint: "" +# volumes: +# - /etc/letsencrypt:/etc/letsencrypt +# - /var/lib/letsencrypt:/var/lib/letsencrypt +# ports: +# - "80" +# - "443" +# environment: +# - TERM=xterm redis: diff --git a/env.example b/env.example index 2324613..db57760 100644 --- a/env.example +++ b/env.example @@ -29,16 +29,16 @@ DJANGO_ACCOUNT_ALLOW_REGISTRATION=True COMPRESS_ENABLED= -PAYMENT_ENVIRONMENT = 'sandbox' +PAYMENT_ENVIRONMENT=sandbox -BRAINTREE_MERCHANT_ID = '' -BRAINTREE_PUBLIC_KEY = '' -BRAINTREE_PRIVATE_KEY = '' +BRAINTREE_MERCHANT_ID=demo +BRAINTREE_PUBLIC_KEY=demo +BRAINTREE_PRIVATE_KEY=demo -PAYPAL_CLIENT_ID = "" -PAYPAL_CLIENT_SECRET = "" +PAYPAL_CLIENT_ID=demo +PAYPAL_CLIENT_SECRET=demo -GOCARDLESS_APP_ID = '' -GOCARDLESS_APP_SECRET = '' -GOCARDLESS_ACCESS_TOKEN = '' -GOCARDLESS_MERCHANT_ID = '' +GOCARDLESS_APP_ID=demo +GOCARDLESS_APP_SECRET=demo +GOCARDLESS_ACCESS_TOKEN=demo +GOCARDLESS_MERCHANT_ID=demo diff --git a/live.yml b/live.yml new file mode 100644 index 0000000..09566d7 --- /dev/null +++ b/live.yml @@ -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 diff --git a/mhackspace/base/management/commands/generate_test_data.py b/mhackspace/base/management/commands/generate_test_data.py new file mode 100644 index 0000000..9bb4471 --- /dev/null +++ b/mhackspace/base/management/commands/generate_test_data.py @@ -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')) diff --git a/mhackspace/feeds/__init__.py b/mhackspace/feeds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/feeds/migrations/0001_initial.py b/mhackspace/feeds/migrations/0001_initial.py index 53bcf3c..4a5c9b9 100644 --- a/mhackspace/feeds/migrations/0001_initial.py +++ b/mhackspace/feeds/migrations/0001_initial.py @@ -1,8 +1,12 @@ # -*- 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 django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import stdimage.models +import stdimage.utils class Migration(migrations.Migration): @@ -13,14 +17,35 @@ class Migration(migrations.Migration): ] 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( name='Feed', fields=[ ('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)), - ('tags', models.CharField(max_length=255)), - ('image', models.ImageField(upload_to='')), + ('tags', models.CharField(blank=True, max_length=255)), + ('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'), + ), ] diff --git a/mhackspace/feeds/migrations/0002_auto_20170104_2033.py b/mhackspace/feeds/migrations/0002_auto_20170104_2033.py deleted file mode 100644 index 4f8bd15..0000000 --- a/mhackspace/feeds/migrations/0002_auto_20170104_2033.py +++ /dev/null @@ -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), - ), - ] diff --git a/mhackspace/feeds/migrations/0003_auto_20170104_2035.py b/mhackspace/feeds/migrations/0003_auto_20170104_2035.py deleted file mode 100644 index 55a38dc..0000000 --- a/mhackspace/feeds/migrations/0003_auto_20170104_2035.py +++ /dev/null @@ -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), - ), - ] diff --git a/mhackspace/feeds/migrations/0004_feed_enabled.py b/mhackspace/feeds/migrations/0004_feed_enabled.py deleted file mode 100644 index 9579887..0000000 --- a/mhackspace/feeds/migrations/0004_feed_enabled.py +++ /dev/null @@ -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), - ), - ] diff --git a/mhackspace/feeds/migrations/0005_storing_articles.py b/mhackspace/feeds/migrations/0005_storing_articles.py deleted file mode 100644 index 0734fba..0000000 --- a/mhackspace/feeds/migrations/0005_storing_articles.py +++ /dev/null @@ -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'), - ), - ] diff --git a/mhackspace/members/views.py b/mhackspace/members/views.py index dbc597c..4518a32 100644 --- a/mhackspace/members/views.py +++ b/mhackspace/members/views.py @@ -10,11 +10,11 @@ from mhackspace.users.models import User class MemberListView(LoginRequiredMixin, ListView): template_name = 'pages/members.html' - queryset = User.objects.prefetch_related('users', 'groups') + queryset = User.objects.prefetch_related('user', 'groups') paginate_by = 10 def get_context_data(self, **kwargs): context = super(MemberListView, self).get_context_data(**kwargs) 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 diff --git a/mhackspace/subscriptions/__init__.py b/mhackspace/subscriptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/subscriptions/management/commands/list_subscription_payments.py b/mhackspace/subscriptions/management/commands/list_subscription_payments.py new file mode 100644 index 0000000..fc575d6 --- /dev/null +++ b/mhackspace/subscriptions/management/commands/list_subscription_payments.py @@ -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) diff --git a/mhackspace/subscriptions/management/commands/list_subscriptions.py b/mhackspace/subscriptions/management/commands/list_subscriptions.py index a4e1d39..dc8f84c 100644 --- a/mhackspace/subscriptions/management/commands/list_subscriptions.py +++ b/mhackspace/subscriptions/management/commands/list_subscriptions.py @@ -24,4 +24,4 @@ class Command(BaseCommand): for sub in provider.fetch_subscriptions(): self.stdout.write( self.style.SUCCESS( - '\t{reference} - {amount} - {status} - {email}'.format(**sub))) + '\t{start_date} {reference} - {amount} - {status} - {email}'.format(**sub))) diff --git a/mhackspace/subscriptions/management/commands/refresh_subscriptions.py b/mhackspace/subscriptions/management/commands/refresh_subscriptions.py new file mode 100644 index 0000000..aeecff9 --- /dev/null +++ b/mhackspace/subscriptions/management/commands/refresh_subscriptions.py @@ -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) + diff --git a/mhackspace/subscriptions/migrations/0001_initial.py b/mhackspace/subscriptions/migrations/0001_initial.py new file mode 100644 index 0000000..4ce244f --- /dev/null +++ b/mhackspace/subscriptions/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/mhackspace/subscriptions/migrations/__init__.py b/mhackspace/subscriptions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mhackspace/subscriptions/models.py b/mhackspace/subscriptions/models.py index e69de29..a2b7770 100644 --- a/mhackspace/subscriptions/models.py +++ b/mhackspace/subscriptions/models.py @@ -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 diff --git a/mhackspace/subscriptions/payments.py b/mhackspace/subscriptions/payments.py index edc4bae..a92bc78 100644 --- a/mhackspace/subscriptions/payments.py +++ b/mhackspace/subscriptions/payments.py @@ -3,7 +3,8 @@ import pytz import gocardless import braintree -from django.conf.settings import payment_providers +from django.conf import settings +payment_providers = settings.PAYMENT_PROVIDERS # import gocardless_pro # import paypalrestsdk as paypal @@ -45,18 +46,48 @@ class gocardless_provider: return { 'amount': subscription.amount, '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): for paying_member in self.client.subscriptions(): user=paying_member.user() - # for bill in paying_member.bills(): - # print('test') - # print(dir(bill)) - # print(bill.created_at) - # print(dir(paying_member)) - # print(paying_member.reference_fields) + + #gocardless does not have a reference so we use the id instead yield { 'status': paying_member.status, 'email': user.email, @@ -70,6 +101,16 @@ class gocardless_provider: def get_token(self): 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'): return gocardless.client.new_subscription_url( amount=float(amount), @@ -199,40 +240,6 @@ class payment: 'reference': paying_member.id, '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): if self.provider == 'gocardless': @@ -362,9 +369,7 @@ class payment: confirm_details['successfull'] = False print('---------------------') print(args) - - from pprint import pprint if self.provider == 'paypal': print(args.get('paymentId')) diff --git a/mhackspace/subscriptions/tests/test_payment_gateways.py b/mhackspace/subscriptions/tests/test_payment_gateways.py index 81c686b..5b19544 100644 --- a/mhackspace/subscriptions/tests/test_payment_gateways.py +++ b/mhackspace/subscriptions/tests/test_payment_gateways.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from test_plus.test import TestCase -# import unittest +from unittest import skip from mock import patch, Mock from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider @@ -26,31 +26,73 @@ class TestPaymentGatewaysGocardless(TestCase): self.provider = gocardless_provider() return self.provider #self.provider - def test_confirm_subscription_callback(self): - with patch('gocardless.client.confirm_resources') as mock_subscription: - self.provider = gocardless_provider() - - def test_fetch_subscription_gocardless(self): - items = [Mock( + @skip("Need to implement") + @patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True) + def test_unsubscribe(self, mock_subscription): + mock_subscription.return_value = Mock(success='success') + mock_subscription.cancel.return_value = Mock( id='01', status='active', amount=20.00, - reference='ref01', 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.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(): self.assertEqual(item.get('status'), 'active') 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('amount'), 20.00) -class TestPaymentGatewaysBraintree(TestCase): +class DisabledestPaymentGatewaysBraintree(TestCase): @patch('mhackspace.subscriptions.payments.braintree.Configuration.configure') def auth_braintree(self, mock_request): # mock braintree initalisation request diff --git a/mhackspace/templates/base.html b/mhackspace/templates/base.html index 2751ea0..aa5764b 100644 --- a/mhackspace/templates/base.html +++ b/mhackspace/templates/base.html @@ -91,8 +91,25 @@ -
+ +
{% if messages %} {% for message in messages %}
{{ message }}
diff --git a/mhackspace/templates/pages/home.html b/mhackspace/templates/pages/home.html index 3220ea9..61c4caf 100644 --- a/mhackspace/templates/pages/home.html +++ b/mhackspace/templates/pages/home.html @@ -4,5 +4,7 @@ {% block content %}

Introduction

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 %} {% endblock content %} diff --git a/mhackspace/templates/pages/members.html b/mhackspace/templates/pages/members.html index e009e7c..265c63a 100644 --- a/mhackspace/templates/pages/members.html +++ b/mhackspace/templates/pages/members.html @@ -4,8 +4,8 @@ Members feed
-
Users {{ paginator.count }}
-
Members {{ total }}
+
Users {{ paginator.count }}
+
Members {{ total }}
@@ -17,13 +17,14 @@ Members feed

{{ member.name }}

{{ member.users.description }}

{{ member.users.skills }}

+

{{ member.status }}

{% for group in member.groups.all %} {{ group.name }} {% endfor %}
-

{{ member.userblurb }}

+

{{ member.blurb }}

View Original diff --git a/mhackspace/templates/users/user_detail.html b/mhackspace/templates/users/user_detail.html index 13f5a1b..e731a00 100644 --- a/mhackspace/templates/users/user_detail.html +++ b/mhackspace/templates/users/user_detail.html @@ -5,12 +5,25 @@ {% block content %}
- +

Profile

Profile image -

{{ user.username }}

{{ user.name }}

{{ user.email }}

@@ -18,13 +31,18 @@

Member since

Description: {{ blurb.description }}

Skills: {{ blurb.description }}

+ +

Membership status

+

Membership Status: {{ membership.get_status }}

+

Last Payment: {{membership.date}}

+

Amount: £{{membership.payment}}

-
Joined
+
Joined {{membership.date}}
-

MHS{{ user.id|stringformat:"05d" }}

Change me

+

MHS{{ user.id|stringformat:"05d" }}

{{user.name}}{{user.last_name}}

Cancel Membership
diff --git a/mhackspace/users/fixtures/groups.json b/mhackspace/users/fixtures/groups.json new file mode 100644 index 0000000..7a946f2 --- /dev/null +++ b/mhackspace/users/fixtures/groups.json @@ -0,0 +1 @@ +[{"model": "auth.group", "pk": 1, "fields": {"name": "members", "permissions": []}}] diff --git a/mhackspace/users/forms.py b/mhackspace/users/forms.py index 73b16ff..4c2b43e 100644 --- a/mhackspace/users/forms.py +++ b/mhackspace/users/forms.py @@ -2,9 +2,9 @@ from django.db import models from django.forms import ModelForm -from .models import UserBlurb +from .models import Blurb -class UserBlurbForm(ModelForm): +class BlurbForm(ModelForm): class Meta: - model = UserBlurb + model = Blurb exclude = ['user'] diff --git a/mhackspace/users/migrations/0001_initial.py b/mhackspace/users/migrations/0001_initial.py index e76c333..def69a6 100644 --- a/mhackspace/users/migrations/0001_initial.py +++ b/mhackspace/users/migrations/0001_initial.py @@ -1,11 +1,14 @@ # -*- 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 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 +import stdimage.models 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')), ('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')), + ('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')), ('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={ - 'verbose_name_plural': 'users', 'verbose_name': 'user', 'abstract': False, + 'verbose_name_plural': 'users', }, managers=[ ('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)), + ], + ), ] diff --git a/mhackspace/users/migrations/0002_membership_userblurb.py b/mhackspace/users/migrations/0002_membership_userblurb.py deleted file mode 100644 index 4e0c458..0000000 --- a/mhackspace/users/migrations/0002_membership_userblurb.py +++ /dev/null @@ -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)), - ], - ), - ] diff --git a/mhackspace/users/migrations/0003_user_image.py b/mhackspace/users/migrations/0003_user_image.py deleted file mode 100644 index f55e304..0000000 --- a/mhackspace/users/migrations/0003_user_image.py +++ /dev/null @@ -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'), - ), - ] diff --git a/mhackspace/users/migrations/0004_auto_20170106_2030.py b/mhackspace/users/migrations/0004_auto_20170106_2030.py deleted file mode 100644 index c98e20c..0000000 --- a/mhackspace/users/migrations/0004_auto_20170106_2030.py +++ /dev/null @@ -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/'), - ), - ] diff --git a/mhackspace/users/migrations/0005_auto_20170108_0050.py b/mhackspace/users/migrations/0005_auto_20170108_0050.py deleted file mode 100644 index 2d05065..0000000 --- a/mhackspace/users/migrations/0005_auto_20170108_0050.py +++ /dev/null @@ -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), - ), - ] diff --git a/mhackspace/users/models.py b/mhackspace/users/models.py index 9c2b3e8..a021f33 100644 --- a/mhackspace/users/models.py +++ b/mhackspace/users/models.py @@ -29,14 +29,44 @@ class User(AbstractUser): return reverse('users:detail', kwargs={'username': self.username}) -class UserBlurb(models.Model): - user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='users') +class Blurb(models.Model): + user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='+') skills = models.CharField(max_length=255) description = models.TextField() -class UserMembership(models.Model): - user = models.ForeignKey(settings.AUTH_USER_MODEL) - payment = models.DecimalField(max_digits=6, decimal_places=2) +MEMBERSHIP_STRING = { + 0: 'Guest user', + 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() 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 diff --git a/mhackspace/users/views.py b/mhackspace/users/views.py index bbdfb07..e511bcd 100644 --- a/mhackspace/users/views.py +++ b/mhackspace/users/views.py @@ -7,10 +7,10 @@ from django.views.generic import DetailView, ListView, RedirectView, UpdateView from django.contrib.auth.mixins import LoginRequiredMixin from .models import User -from .models import UserBlurb -from .models import UserMembership +from .models import Blurb +from .models import Membership -from .forms import UserBlurbForm +from .forms import BlurbForm class UserDetailView(LoginRequiredMixin, DetailView): model = User @@ -21,8 +21,8 @@ class UserDetailView(LoginRequiredMixin, DetailView): def get_context_data(self, **kwargs): # xxx will be available in the template as the related objects context = super(UserDetailView, self).get_context_data(**kwargs) - context['blurb'] = UserBlurb.objects.filter(user=self.get_object()).first() - context['membership'] = UserMembership.objects.filter(user=self.get_object()).first() + context['blurb'] = Blurb.objects.filter(user=self.get_object()).first() + context['membership'] = Membership.objects.filter(user=self.get_object()).first() return context class UserRedirectView(LoginRequiredMixin, RedirectView): @@ -45,8 +45,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): def get_context_data(self, **kwargs): context = super(UserUpdateView, self).get_context_data(**kwargs) - profile = UserBlurb.objects.filter(user=self.get_object()).first() - context['form_blurb'] = UserBlurbForm(instance=profile) + profile = Blurb.objects.filter(user=self.get_object()).first() + context['form_blurb'] = BlurbForm(instance=profile) return context def get_object(self): @@ -54,8 +54,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): return User.objects.get(username=self.request.user.username) def form_valid(self, form): - profile = UserBlurb.objects.filter(user=self.get_object()).first() - form_blurb = UserBlurbForm(self.request.POST, instance=profile) + profile = Blurb.objects.filter(user=self.get_object()).first() + form_blurb = BlurbForm(self.request.POST, instance=profile) if form_blurb.is_valid(): blurb_model = form_blurb.save(commit=False) blurb_model.user = self.request.user diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b5774c9 --- /dev/null +++ b/requirements.txt @@ -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 + diff --git a/requirements/base.txt b/requirements/base.txt index b8623fa..1adde01 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -42,16 +42,19 @@ django-redis==4.7.0 redis>=2.10.5 - - -rcssmin==1.0.6 +rcssmin==1.0.6 django-compressor==2.1 +django-sass-processor==0.5.3 lxml==3.7.2 + + +# Your custom requirements go here mock==2.0.0 gocardless -braintree +braintree==3.34.0 -# Your custom requirements go here --e git+https://github.com/olymk2/scaffold.git#egg=scaffold +django-autofixture==0.12.1 + +git+https://github.com/olymk2/scaffold.git diff --git a/staticfiles/staticfiles b/staticfiles/staticfiles new file mode 100644 index 0000000..e69de29