Merge from upstream/master
This commit is contained in:
commit
de4f11878b
|
@ -15,7 +15,7 @@ pipeline:
|
|||
- cp -n env.example .env
|
||||
- mkdir -p ./cache/packages ./cache/pip
|
||||
- pip install --user --cache-dir ./cache/pip -r ./requirements/test.txt
|
||||
- python manage.py test mhackspace --verbosity 2
|
||||
- python manage.py test mhackspace --keepdb --verbosity 2
|
||||
|
||||
publish-test:
|
||||
pull: True
|
||||
|
|
24
README.org
24
README.org
|
@ -5,7 +5,6 @@
|
|||
|
||||
Repository for the maidstone hackspace website, feel free to fork this site for your own Hackspace.
|
||||
|
||||
|
||||
** Requirements
|
||||
Before getting started make sure you have git, docker and docker-compose installed on your machine.
|
||||
The simplest way to setup this site is to use docker-compose so please install that from this site
|
||||
|
@ -47,7 +46,7 @@ docker-compose -f dev.yml run --rm django python manage.py makemigrations
|
|||
docker-compose -f dev.yml run --rm django python manage.py migrate
|
||||
#+END_SRC
|
||||
*** Create the admin user.
|
||||
Once created you can login at http://127.0.0.1:8180/admin
|
||||
Once created you can login at http://127.0.0.1:8180/trustee
|
||||
#+BEGIN_SRC sh
|
||||
docker-compose -f dev.yml run --rm django python manage.py createsuperuser
|
||||
#+END_SRC
|
||||
|
@ -74,3 +73,24 @@ docker-compose -fdev.yml run --rm django python manage.py list_subscriptions
|
|||
#+BEGIN_SRC sh
|
||||
docker-compose -fdev.yml run --rm django python manage.py rendervariations 'blog.Post.image' --replace
|
||||
#+END_SRC
|
||||
** Api
|
||||
#+BEGIN_SRC python
|
||||
import requests
|
||||
|
||||
url = 'http://127.0.0.1:8180/api/v1/rfidAuth/'
|
||||
data = {
|
||||
'rfid': '4996',
|
||||
'device': '7bff6053-77ef-4250-ac11-8a119fd05a0e'
|
||||
}
|
||||
|
||||
# client = RequestsClient()
|
||||
response = requests.post(
|
||||
'http://127.0.0.1:8180/api/v1/rfidAuth/',
|
||||
data={'rfid': '238e', 'device': 'e8f27231-8093-4477-8906-e5ae1b12dbd6'})
|
||||
#requests.get(url)
|
||||
return response.status_code
|
||||
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
: 200
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
python manage.py migrate
|
||||
python manage.py runserver_plus 0.0.0.0:8000
|
||||
while true; do python manage.py runserver_plus 0.0.0.0:8000; sleep 2; done
|
||||
|
|
|
@ -82,6 +82,7 @@ THIRD_PARTY_APPS = (
|
|||
'whitenoise.runserver_nostatic',
|
||||
'stdimage',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
'draceditor',
|
||||
'haystack',
|
||||
'djconfig',
|
||||
|
@ -140,7 +141,7 @@ LOCAL_APPS = (
|
|||
'mhackspace.blog',
|
||||
'mhackspace.core',
|
||||
'mhackspace.requests',
|
||||
'mhackspace.register',
|
||||
'mhackspace.rfid',
|
||||
)
|
||||
|
||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
|
@ -376,7 +377,12 @@ CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='redis://redis:6379/0')
|
|||
#if CELERY_BROKER_URL == 'django://':
|
||||
# CELERY_RESULT_BACKEND = 'redis://'
|
||||
#else:
|
||||
CELERY_RESULT_BACKEND = 'django-cache'
|
||||
CELERY_RESULT_BACKEND = 'redis://redis:6379/0'
|
||||
CELERY_IGNORE_RESULT = False
|
||||
CELERY_REDIS_HOST = "redis"
|
||||
CELERY_REDIS_PORT = 6379
|
||||
CELERY_REDIS_DB = 0
|
||||
|
||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||
INSTALLED_APPS += ('django_celery_results','django_celery_beat',)
|
||||
CELERY_TIMEZONE = 'UTC'
|
||||
|
@ -449,10 +455,12 @@ REST_FRAMEWORK = {
|
|||
'rest_framework.filters.OrderingFilter'
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
|
||||
# 'rest_framework.permissions.IsAuthenticated',
|
||||
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
|
||||
# 'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
),
|
||||
|
@ -472,3 +480,4 @@ MATRIX_USER=env('MATRIX_USERNAME')
|
|||
MATRIX_PASSWORD=env('MATRIX_PASSWORD')
|
||||
MATRIX_ROOM=env('MATRIX_ROOM')
|
||||
MSG_PREFIX = 'MH'
|
||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||
|
|
|
@ -86,7 +86,7 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
|||
|
||||
########## CELERY
|
||||
# In development, all tasks will be executed locally by blocking until the task returns
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
# CELERY_ALWAYS_EAGER = True
|
||||
########## END CELERY
|
||||
|
||||
# Your local stuff: Below this line define 3rd party library settings
|
||||
|
@ -99,5 +99,50 @@ CAPTCHA = {
|
|||
WHITENOISE_AUTOREFRESH = True
|
||||
WHITENOISE_USE_FINDERS = True
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse'
|
||||
}
|
||||
},
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '%(levelname)s %(asctime)s %(module)s '
|
||||
'%(process)d %(thread)d %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'DEBUG',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'logfile': {
|
||||
'level':'DEBUG',
|
||||
'class':'logging.FileHandler',
|
||||
'filename': "%s/django.log" % ROOT_DIR,
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins', 'logfile'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True
|
||||
},
|
||||
'django.security.DisallowedHost': {
|
||||
'level': 'ERROR',
|
||||
'handlers': ['logfile', 'console', 'mail_admins'],
|
||||
'propagate': True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PAYMENT_PROVIDERS['gocardless']['redirect_url'] = 'http://127.0.0.1:8180'
|
||||
TEMPLATE_DEBUG = False
|
||||
|
|
|
@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
|
|||
CSRF_COOKIE_SECURE = True
|
||||
#disabledd so csrf works with ajax
|
||||
CSRF_COOKIE_HTTPONLY = False
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||
|
||||
# SITE CONFIGURATION
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
|
|||
CSRF_COOKIE_SECURE = True
|
||||
#disabledd so csrf works with ajax
|
||||
CSRF_COOKIE_HTTPONLY = False
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||
|
||||
# SITE CONFIGURATION
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -67,5 +67,8 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
|
|||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {'ENGINE': 'django.db.backends.sqlite3'}
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(str(ROOT_DIR), 'cache/test_database.db'),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
|
@ -9,6 +10,7 @@ from django.views import defaults as default_views
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.documentation import include_docs_urls
|
||||
|
||||
from mhackspace.contact.views import contact
|
||||
from mhackspace.members.views import MemberListView
|
||||
|
@ -20,22 +22,29 @@ from mhackspace.blog.views import PostViewSet, CategoryViewSet, BlogPost, PostLi
|
|||
from mhackspace.blog.sitemaps import PostSitemap, CategorySitemap
|
||||
from mhackspace.feeds.views import FeedViewSet, ArticleViewSet
|
||||
from mhackspace.requests.views import RequestsForm, RequestsList
|
||||
from mhackspace.rfid.views import DeviceViewSet, AuthUserWithDeviceViewSet
|
||||
|
||||
from mhackspace.register.views import RegisterForm
|
||||
|
||||
from wiki.urls import get_pattern as get_wiki_pattern
|
||||
from django_nyt.urls import get_pattern as get_nyt_pattern
|
||||
from rest_framework_jwt.views import obtain_jwt_token
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'posts', PostViewSet)
|
||||
router.register(r'categories', CategoryViewSet)
|
||||
router.register(r'feeds', FeedViewSet)
|
||||
router.register(r'articles', ArticleViewSet)
|
||||
router.register(r'posts', PostViewSet, 'posts')
|
||||
router.register(r'categories', CategoryViewSet, base_name='categories')
|
||||
router.register(r'feeds', FeedViewSet, 'feeds')
|
||||
router.register(r'articles', ArticleViewSet, base_name='articles')
|
||||
router.register(r'rfid', DeviceViewSet, base_name='rfid_device')
|
||||
router.register(r'rfidAuth', AuthUserWithDeviceViewSet, base_name='device_auth')
|
||||
|
||||
|
||||
sitemaps = {
|
||||
'posts': PostSitemap,
|
||||
'category': CategorySitemap,
|
||||
}
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'),
|
||||
url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'),
|
||||
|
@ -69,6 +78,9 @@ urlpatterns = [
|
|||
# Django Admin, use {% url 'admin:index' %}
|
||||
url(r'{}'.format(settings.ADMIN_URL), admin.site.urls),
|
||||
|
||||
url(r'^api-token-auth/', obtain_jwt_token),
|
||||
url(r'^api/docs/', include_docs_urls(title='Hackspace API')),
|
||||
|
||||
# User management
|
||||
url(r'^users/', include('mhackspace.users.urls', namespace='users')),
|
||||
url(r'^accounts/', include('allauth.urls')),
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
import uuid
|
||||
import random
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from autofixture import AutoFixture
|
||||
from autofixture.generators import ImageGenerator
|
||||
from autofixture.generators import (
|
||||
ImageGenerator,
|
||||
IntegerGenerator,
|
||||
ChoicesGenerator,
|
||||
Generator,
|
||||
LoremWordGenerator)
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management import call_command
|
||||
from mhackspace.base.models import BannerImage
|
||||
from mhackspace.feeds.models import Article, Feed
|
||||
from mhackspace.users.models import User
|
||||
from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
|
||||
from mhackspace.blog.models import Category, Post
|
||||
from mhackspace.rfid.models import Device, DeviceAuth
|
||||
|
||||
|
||||
class ImageFixture(AutoFixture):
|
||||
|
@ -14,12 +22,23 @@ class ImageFixture(AutoFixture):
|
|||
scaled_image = ImageGenerator(width=800, height=300, sizes=((1280, 300),))
|
||||
|
||||
|
||||
def RfidFixture():
|
||||
while True:
|
||||
yield str(uuid.uuid4())[0:4]
|
||||
|
||||
|
||||
class RfidGenerator(Generator):
|
||||
def generate(self):
|
||||
return str(uuid.uuid4())[0:4]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Build test data for development environment'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
feeds = AutoFixture(Article)
|
||||
feeds = AutoFixture(Article, generate_fk=True)
|
||||
feeds.create(10)
|
||||
|
||||
feed = AutoFixture(Feed)
|
||||
feed.create(10)
|
||||
|
||||
|
@ -32,14 +51,61 @@ class Command(BaseCommand):
|
|||
# load known data
|
||||
call_command('loaddata', 'mhackspace/users/fixtures/groups.json', verbose=0)
|
||||
|
||||
# random data
|
||||
|
||||
User.objects.all().delete()
|
||||
Membership.objects.all().delete()
|
||||
users = AutoFixture(User, field_values={
|
||||
'title': random.choicee(('Mr', 'Mrs', 'Emperor', 'Captain'))
|
||||
'title': ChoicesGenerator(values=('Mr', 'Mrs', 'Emperor', 'Captain')),
|
||||
'password': make_password('autofixtures'),
|
||||
'active': True,
|
||||
'username': ChoicesGenerator(values=('Bob', 'Jane', 'Adam', 'Alice', 'Bill', 'Jill', 'Sam', 'Oly'))
|
||||
}, generate_fk=True)
|
||||
users.create(8)
|
||||
users = AutoFixture(User, field_values={
|
||||
'title': 'Mr',
|
||||
'username': 'admin',
|
||||
'password': make_password('autofixtures'),
|
||||
'is_superuser': True,
|
||||
'is_staff': True,
|
||||
'is_active': True
|
||||
}, generate_fk=True)
|
||||
users.create(1)
|
||||
|
||||
user_list = User.objects.all()
|
||||
members = AutoFixture(Membership, field_values={
|
||||
'status': ChoicesGenerator(MEMBERSHIP_STATUS_CHOICES),
|
||||
'user': ChoicesGenerator(values=user_list)
|
||||
})
|
||||
users.create(10)
|
||||
members.create(8)
|
||||
|
||||
Rfid.objects.all().delete()
|
||||
Device.objects.all().delete()
|
||||
DeviceAuth.objects.all().delete()
|
||||
|
||||
rfid = AutoFixture(
|
||||
Rfid,
|
||||
field_values={
|
||||
'code': RfidGenerator(),
|
||||
'description': LoremWordGenerator()})
|
||||
rfid.create(20)
|
||||
|
||||
device = AutoFixture(Device, field_values={
|
||||
'name': ChoicesGenerator(values=('Door', 'Printer', 'Laser Cutter', ''))
|
||||
})
|
||||
device.create(5)
|
||||
|
||||
deviceauth = AutoFixture(DeviceAuth)
|
||||
deviceauth.create(5)
|
||||
|
||||
feed = AutoFixture(Feed)
|
||||
feed.create(10)
|
||||
|
||||
feeds = AutoFixture(Article)
|
||||
feeds.create(10)
|
||||
|
||||
banners = ImageFixture(BannerImage)
|
||||
banners.create(10)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
'Finished creating test data'))
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ from mhackspace.feeds.helper import import_feeds
|
|||
|
||||
@shared_task
|
||||
def update_homepage_feeds():
|
||||
return import_feeds()
|
||||
|
||||
import_feeds()
|
||||
return {'result': 'Homepage feeds imported'}
|
||||
|
||||
matrix_url = "https://matrix.org/_matrix/client/r0"
|
||||
matrix_login_url = matrix_url + "/login"
|
||||
|
@ -31,9 +31,10 @@ def send_email(email_to,
|
|||
to=[email_to],
|
||||
headers={'Reply-To': 'no-reply@maidstone-hackspace.org.uk'})
|
||||
email.send()
|
||||
return {'result', 'Email sent to %s' % email_to}
|
||||
|
||||
@shared_task
|
||||
def matrix_message(message):
|
||||
def matrix_message(message, prefix=''):
|
||||
# we dont rely on theses, so ignore if it goes wrong
|
||||
# TODO at least log that something has gone wrong
|
||||
try:
|
||||
|
@ -59,8 +60,8 @@ def matrix_message(message):
|
|||
url = matrix_send_msg_url.format(**url_params)
|
||||
details = {
|
||||
"msgtype": "m.text",
|
||||
"body": "[%s] %s" % (settings.MSG_PREFIX, message)}
|
||||
"body": "[%s%s] %s" % (settings.MSG_PREFIX, prefix, message)}
|
||||
r2 = requests.post(url, json=details)
|
||||
except:
|
||||
pass
|
||||
return True
|
||||
return {'result', 'Matrix message sent successfully'}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
from test_plus.test import TestCase
|
||||
from mhackspace.users.models import Membership
|
||||
from mhackspace.users.models import User
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
|
||||
|
||||
# this needs mocking
|
||||
class TestTasks(TestCase):
|
||||
def setUp(self):
|
||||
self.user1 = self.make_user('u1')
|
||||
self.user2 = self.make_user('u2')
|
||||
self.group = Group(name='members')
|
||||
self.group.save()
|
||||
|
||||
def test_refresh_subscriptions(self):
|
||||
membership_count = Membership.objects.all().delete()
|
||||
user_count = User.objects.all().count()
|
||||
membership_count = Membership.objects.all().count()
|
||||
self.assertEquals(0, membership_count)
|
||||
self.assertEquals(2, user_count)
|
||||
|
||||
update_subscriptions(provider_name='gocardless')
|
||||
|
||||
membership_count = Membership.objects.all().count()
|
||||
self.assertEquals(2, membership_count)
|
||||
self.assertEquals(2, user_count)
|
|
@ -53,6 +53,8 @@ def download_remote_images():
|
|||
render_variations(result[0], image_variations, replace=True)
|
||||
article.save()
|
||||
except:
|
||||
logger.exception(result)
|
||||
logger.exception(result[0])
|
||||
logger.exception('Unable to download remote image for %s' % article.original_image)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-19 20:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('requests', '0006_auto_20170906_0804'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userrequests',
|
||||
name='cost',
|
||||
field=models.DecimalField(decimal_places=2, help_text='Leave blank, if no associated cost, or add estimated cost if not sure.', max_digits=6),
|
||||
),
|
||||
]
|
|
@ -21,7 +21,7 @@ class UserRequests(models.Model):
|
|||
title = models.CharField(max_length=255, help_text='Whats being requested ?')
|
||||
request_type = models.IntegerField(choices=REQUEST_TYPES, null=False)
|
||||
cost = models.DecimalField(
|
||||
max_digits=4,
|
||||
max_digits=6,
|
||||
decimal_places=2,
|
||||
help_text='Leave blank, if no associated cost, or add estimated cost if not sure.'
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ class UserRequests(models.Model):
|
|||
|
||||
|
||||
def send_topic_update_email(sender, instance, **kwargs):
|
||||
matrix_message.delay('New Request - %s' % instance.title)
|
||||
matrix_message.delay(prefix=' - REQUEST', message=instance.title)
|
||||
|
||||
|
||||
post_save.connect(send_topic_update_email, sender=UserRequests)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
from django.test import TestCase
|
||||
from mhackspace.requests.views import RequestsList, RequestForm
|
||||
from mhackspace.users.models import User
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
# @pytest.mark.parametrize("version", versions)
|
||||
# @pytest.mark.parametrize("test_ctx, name", contexts)
|
||||
# def test_context_renders(name, test_ctx, version):
|
||||
|
||||
# users = AutoFixture(User, field_values={
|
||||
# 'title': 'Mr',
|
||||
# 'username': 'admin',
|
||||
# 'password': make_password('autofixtures'),
|
||||
# 'is_superuser': True,
|
||||
# 'is_staff': True,
|
||||
# 'is_active': True
|
||||
# }, generate_fk=True)
|
||||
|
||||
def all_user_types():
|
||||
users = AutoFixture(User, field_values={
|
||||
'title': 'Mr',
|
||||
'username': 'admin',
|
||||
'password': make_password('autofixtures'),
|
||||
}, generate_fk=True)
|
||||
yield users.create(1)
|
||||
|
||||
users = AutoFixture(User, field_values={
|
||||
'title': 'Mr',
|
||||
'username': 'admin',
|
||||
'password': make_password('autofixtures'),
|
||||
'is_staff': True,
|
||||
}, generate_fk=True)
|
||||
yield users.create(1)
|
||||
|
||||
users = AutoFixture(User, field_values={
|
||||
'title': 'Mr',
|
||||
'username': 'admin',
|
||||
'password': make_password('autofixtures'),
|
||||
'is_superuser': True,
|
||||
'is_staff': True,
|
||||
}, generate_fk=True)
|
||||
yield users.create(1)
|
||||
|
||||
class BaseUserTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def testRequestView(self):
|
||||
for user in all_user_types()
|
||||
view = RequestsList()
|
||||
request = self.factory.get('/fake-url')
|
||||
request.user = user
|
||||
view.request = request
|
||||
|
||||
|
||||
# class TestUserUpdateView(BaseUserTestCase):
|
||||
|
||||
# def setUp(self):
|
||||
# # call BaseUserTestCase.setUp()
|
||||
# super(TestUserUpdateView, self).setUp()
|
||||
# # Instantiate the view directly. Never do this outside a test!
|
||||
# self.view = UserUpdateView()
|
||||
# # Generate a fake request
|
||||
# request = self.factory.get('/fake-url')
|
||||
# # Attach the user to the request
|
||||
# request.user = self.user
|
||||
# # Attach the request to the view
|
||||
# self.view.request = request
|
||||
|
||||
# def test_get_success_url(self):
|
||||
# # Expect: '/users/testuser/', as that is the default username for
|
||||
# # self.make_user()
|
||||
# self.assertEqual(
|
||||
# self.view.get_success_url(),
|
||||
# '/users/testuser/'
|
||||
# )
|
||||
|
||||
# def test_get_object(self):
|
||||
# # Expect: self.user, as that is the request's user object
|
||||
# self.assertEqual(
|
||||
# self.view.get_object(),
|
||||
# self.user
|
||||
# )
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.forms.models import ModelChoiceField
|
||||
|
||||
from mhackspace.rfid.models import Device, DeviceAuth
|
||||
|
||||
|
||||
@admin.register(Device)
|
||||
class DeviceAdmin(ModelAdmin):
|
||||
list_display = ('name', 'identifier')
|
||||
|
||||
|
||||
@admin.register(DeviceAuth)
|
||||
class DeviceAuthAdmin(ModelAdmin):
|
||||
list_display = ('device', 'rfid_code', 'rfid_user', 'device_id')
|
||||
|
||||
def rfid_code(self, x):
|
||||
return x.rfid.code
|
||||
|
||||
def rfid_user(self, x):
|
||||
return x.rfid.user
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-04-27 18:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('users', '0004_rfid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Device',
|
||||
fields=[
|
||||
('identifier', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=255, verbose_name='Device name')),
|
||||
('description', models.CharField(blank=True, max_length=255, verbose_name='Short description of what the device does')),
|
||||
('added_date', models.DateTimeField(default=django.utils.timezone.now, editable=False)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceAuth',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rfid.Device')),
|
||||
('rfid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Rfid')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='members',
|
||||
field=models.ManyToManyField(through='rfid.DeviceAuth', to='users.Rfid'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import uuid
|
||||
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mhackspace.users.models import Rfid
|
||||
# just brainstorming so we can start playing with this,
|
||||
# be nice to make this a 3rd party django installable app ?
|
||||
|
||||
# description of a device like door, print, laser cutter
|
||||
class Device(models.Model):
|
||||
# user = models.ManyToMany(settings.AUTH_USER_MODEL)
|
||||
name = models.CharField(_('Device name'), max_length=255)
|
||||
identifier = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
description = models.CharField(_('Short description of what the device does'), blank=True, max_length=255)
|
||||
added_date = models.DateTimeField(default=timezone.now, editable=False)
|
||||
members = models.ManyToManyField(Rfid, through='DeviceAuth')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
# http://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields
|
||||
# many to many, lookup user from rfid model then get there user_id and the device to check if auth allowed
|
||||
class DeviceAuth(models.Model):
|
||||
rfid = models.ForeignKey(
|
||||
Rfid,
|
||||
)
|
||||
|
||||
device = models.ForeignKey(
|
||||
Device,
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
from rest_framework import serializers
|
||||
from mhackspace.rfid.models import Device
|
||||
|
||||
|
||||
class Task(object):
|
||||
def __init__(self, **kwargs):
|
||||
for field in ('id', 'name', 'owner', 'status'):
|
||||
setattr(self, field, kwargs.get(field, None))
|
||||
|
||||
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
added_date = serializers.DateTimeField(format='iso-8601')
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ('__all__')
|
||||
|
||||
|
||||
class AuthSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=255)
|
||||
rfid = serializers.CharField(max_length=255)
|
||||
# device = serializers.UUIDField(format='hex_verbose')
|
||||
device = serializers.CharField(max_length=255)
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import sys
|
||||
import requests
|
||||
|
||||
from io import StringIO
|
||||
from django.core.management import call_command
|
||||
from test_plus.test import TestCase
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from rest_framework.test import RequestsClient
|
||||
|
||||
from mhackspace.rfid.models import Device, DeviceAuth
|
||||
from mhackspace.users.models import User, Rfid
|
||||
|
||||
|
||||
# http://www.django-rest-framework.org/api-guide/testing/
|
||||
|
||||
class MigrationTestCase(TestCase):
|
||||
|
||||
def testRollback(self):
|
||||
out = StringIO()
|
||||
sys.stout = out
|
||||
call_command('migrate', 'rfid', 'zero', stdout=out)
|
||||
call_command('migrate', 'rfid', stdout=out)
|
||||
self.assertIn("... OK\n", out.getvalue())
|
||||
|
||||
|
||||
class ApiTests(TestCase):
|
||||
maxDiff = None
|
||||
def setUp(self):
|
||||
self.user = User(name='User01')
|
||||
self.user.save()
|
||||
self.device = Device(
|
||||
name='device01',
|
||||
identifier='8e274b70-a4b3-4600-9472-f20ea7828cb6')
|
||||
self.device.save()
|
||||
self.rfid = Rfid(code='1', user=self.user)
|
||||
self.rfid.save()
|
||||
self.auth = DeviceAuth(rfid=self.rfid, device=self.device)
|
||||
self.auth.save()
|
||||
|
||||
def testAuth(self):
|
||||
factory = APIRequestFactory()
|
||||
request = factory.get('/rfid/')
|
||||
|
||||
def testValidAuthCase(self):
|
||||
"if we have a user rfid and a device identifier"
|
||||
client = RequestsClient()
|
||||
response = client.post(
|
||||
'http://127.0.0.1:8180/api/v1/rfidAuth/',
|
||||
data={'rfid': '1', 'device': self.device.identifier})
|
||||
assert response.status_code == 200
|
||||
expected_result = {
|
||||
'rfid': self.rfid.code,
|
||||
'name': 'device01',
|
||||
'device': str(self.device.identifier)}
|
||||
self.assertEquals(
|
||||
response.json(),
|
||||
expected_result
|
||||
)
|
||||
|
||||
def testInValidAuthCase(self):
|
||||
client = RequestsClient()
|
||||
response = client.post(
|
||||
'http://127.0.0.1:8180/api/v1/rfidAuth/',
|
||||
data={'rfid': '99', 'device': str(self.device.identifier)})
|
||||
assert response.status_code == 404
|
||||
|
||||
# response = client.post(
|
||||
# 'http://127.0.0.1:8180/api/v1/rfidAuth/',
|
||||
# data={'rfid': '1', 'device': 'test%s' % str(self.device.identifier)[3:]})
|
||||
# assert response.status_code == 404
|
||||
|
||||
|
||||
def testAuthUserWithDevice(self):
|
||||
client = RequestsClient()
|
||||
response = client.get('http://127.0.0.1:8180/api/v1/rfid/?format=json')
|
||||
assert response.status_code == 200
|
||||
self.assertEquals(response.json().get('results'), [{
|
||||
'name': 'device01',
|
||||
'identifier': '8e274b70-a4b3-4600-9472-f20ea7828cb6',
|
||||
'members': [1],
|
||||
'added_date': self.device.added_date.isoformat().replace('+00:00', 'Z'),
|
||||
'description': ''
|
||||
}])
|
||||
|
||||
def testFetchDeviceList(self):
|
||||
client = RequestsClient()
|
||||
response = client.get('http://127.0.0.1:8180/api/v1/rfid/?format=json')
|
||||
assert response.status_code == 200
|
||||
self.assertEquals(response.json().get('results'), [{
|
||||
'name': 'device01',
|
||||
'identifier': '8e274b70-a4b3-4600-9472-f20ea7828cb6',
|
||||
'members': [1],
|
||||
'added_date': self.device.added_date.isoformat().replace('+00:00', 'Z'),
|
||||
'description': ''
|
||||
}])
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import logging
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets
|
||||
from rest_framework import status
|
||||
from mhackspace.users.models import Rfid
|
||||
from mhackspace.rfid.models import Device, DeviceAuth
|
||||
from mhackspace.rfid.serializers import DeviceSerializer, AuthSerializer
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeviceViewSet(viewsets.ModelViewSet):
|
||||
queryset = Device.objects.all()
|
||||
serializer_class = DeviceSerializer
|
||||
|
||||
|
||||
# https://medium.com/django-rest-framework/django-rest-framework-viewset-when-you-don-t-have-a-model-335a0490ba6f
|
||||
class AuthUserWithDeviceViewSet(viewsets.ViewSet):
|
||||
# http_method_names = ['post']
|
||||
serializer_class = AuthSerializer
|
||||
|
||||
def list(self, request):
|
||||
serializer = DeviceSerializer(
|
||||
Device.objects.all(), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def post(self, request, format=None):
|
||||
try:
|
||||
rfid = Rfid.objects.get(code=request.data.get('rfid'))
|
||||
device = Device.objects.get(identifier=request.data.get('device'))
|
||||
deviceAuth = DeviceAuth.objects.get(device=device.identifier, rfid=rfid.id)
|
||||
except ObjectDoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
except ValidationError as e:
|
||||
# except:
|
||||
# logger.exception("An error occurred")
|
||||
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
serializer = AuthSerializer(
|
||||
instance={'name': device.name, 'rfid': rfid.code, 'device': device.identifier})
|
||||
return Response(serializer.data, status=200)
|
|
@ -0,0 +1,20 @@
|
|||
.modal-body iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.modal-dialog {
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin: 50px;
|
||||
padding: 0;
|
||||
max-width: None;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -9,6 +9,7 @@
|
|||
@import "components/feeds";
|
||||
|
||||
@import "components/blog";
|
||||
@import "components/wiki";
|
||||
|
||||
////////////////////////////////
|
||||
//Django Toolbar//
|
||||
|
@ -21,3 +22,9 @@
|
|||
[hidden][style="display: block;"] {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.imgfit {
|
||||
width:100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
from django.contrib import messages
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from mhackspace.users.models import Membership
|
||||
from mhackspace.users.models import MEMBERSHIP_CANCELLED, MEMBERSHIP_ACTIVE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_or_update_membership(user, signup_details, complete=False):
|
||||
start_date = signup_details.get('start_date')
|
||||
if not isinstance(start_date, datetime):
|
||||
start_date = parse_datetime(start_date)
|
||||
try:
|
||||
member = Membership.objects.get(user=user)
|
||||
member = Membership.objects.get(email=signup_details.get('email'))
|
||||
# Only update if newer than last record, this way we only get the latest status
|
||||
# cancellation and changed payment will not be counted against current status
|
||||
if start_date < member.date:
|
||||
return True
|
||||
except Membership.DoesNotExist:
|
||||
member = Membership()
|
||||
member.user = user
|
||||
|
||||
if complete is True:
|
||||
member.status = Membership.lookup_status(name=signup_details.get('status'))
|
||||
member.status = MEMBERSHIP_ACTIVE
|
||||
member.email = signup_details.get('email')
|
||||
member.reference = signup_details.get('reference')
|
||||
member.payment = signup_details.get('amount')
|
||||
member.date = signup_details.get('start_date')
|
||||
member.date = start_date
|
||||
|
||||
member.save()
|
||||
|
||||
|
@ -25,6 +37,21 @@ def create_or_update_membership(user, signup_details, complete=False):
|
|||
return False # sign up not completed
|
||||
|
||||
# add user to group on success
|
||||
|
||||
if user:
|
||||
try:
|
||||
group = Group.objects.get(name='members')
|
||||
user.groups.add(group)
|
||||
except:
|
||||
logger.error('Members group does not exist')
|
||||
return True # Sign up finished
|
||||
|
||||
|
||||
def cancel_membership(user):
|
||||
member = Membership.objects.get(user=user)
|
||||
member.status = MEMBERSHIP_CANCELLED
|
||||
member.save()
|
||||
|
||||
group = Group.objects.get(name='members')
|
||||
user.groups.remove(group)
|
||||
return True
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
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
|
||||
|
@ -8,11 +6,14 @@ from mhackspace.users.models import Membership, User
|
|||
from mhackspace.subscriptions.helper import create_or_update_membership
|
||||
|
||||
|
||||
# this should be done in bulk, create the objects and save all at once
|
||||
# for now its not an issue, because of small membership size
|
||||
|
||||
|
||||
def update_subscriptions(provider_name):
|
||||
provider = select_provider('gocardless')
|
||||
|
||||
Membership.objects.all().delete()
|
||||
subscriptions = []
|
||||
|
||||
group = Group.objects.get(name='members')
|
||||
|
||||
|
@ -24,18 +25,10 @@ def update_subscriptions(provider_name):
|
|||
except User.DoesNotExist:
|
||||
user_model = None
|
||||
|
||||
subscriptions.append(
|
||||
Membership(
|
||||
create_or_update_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'))
|
||||
)
|
||||
)
|
||||
yield model_to_dict(subscriptions[-1])
|
||||
signup_details=sub,
|
||||
complete=True)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -49,11 +42,11 @@ class Command(BaseCommand):
|
|||
|
||||
provider = select_provider('gocardless')
|
||||
Membership.objects.all().delete()
|
||||
subscriptions = []
|
||||
|
||||
group = Group.objects.get(name='members')
|
||||
|
||||
for sub in provider.fetch_subscriptions():
|
||||
prefix = ''
|
||||
sub['amount'] = sub['amount'] * 0.01
|
||||
try:
|
||||
user_model = User.objects.get(email=sub.get('email'))
|
||||
|
@ -61,37 +54,23 @@ class Command(BaseCommand):
|
|||
user_model.groups.add(group)
|
||||
except User.DoesNotExist:
|
||||
user_model = None
|
||||
self.style.NOTICE(
|
||||
'\tMissing User {reference} - {payment} - {status} - {email}'.format(**{
|
||||
'reference': sub.get('reference'),
|
||||
'payment': sub.get('amount'),
|
||||
'status': sub.get('status'),
|
||||
'email': sub.get('email')
|
||||
}))
|
||||
continue
|
||||
prefix = 'NO USER - '
|
||||
|
||||
create_or_update_membership(user=user_model,
|
||||
signup_details=sub,
|
||||
complete=True)
|
||||
subscriptions.append(
|
||||
Membership(
|
||||
user=user_model,
|
||||
email=sub.get('email'),
|
||||
reference=sub.get('reference'),
|
||||
payment=sub.get('amount'),
|
||||
date=sub.get('start_date'),
|
||||
status=Membership.lookup_status(name=sub.get('status'))
|
||||
)
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
'\t{reference} - {payment} - {status} - {email}'.format(**{
|
||||
message = '\t{prefix}{date} - {reference} - {payment} - {status} - {email}'.format(**{
|
||||
'prefix': prefix,
|
||||
'date': sub.get('start_date'),
|
||||
'reference': sub.get('reference'),
|
||||
'payment': sub.get('amount'),
|
||||
'status': sub.get('status'),
|
||||
'email': sub.get('email')
|
||||
})))
|
||||
})
|
||||
|
||||
Membership.objects.bulk_create(subscriptions)
|
||||
if user_model:
|
||||
self.stdout.write(self.style.SUCCESS(message))
|
||||
else:
|
||||
self.stdout.write(self.style.NOTICE(message))
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from pprint import pprint
|
||||
import pytz
|
||||
import gocardless_pro as gocardless
|
||||
import gocardless_pro
|
||||
import braintree
|
||||
import logging
|
||||
|
||||
|
@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
|
|||
PROVIDER_ID = {'gocardless':1, 'braintree': 2}
|
||||
PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'}
|
||||
|
||||
|
||||
def select_provider(type):
|
||||
if type == "gocardless": return gocardless_provider()
|
||||
if type == "braintree": return braintree_provider()
|
||||
|
@ -20,6 +21,7 @@ def select_provider(type):
|
|||
log.exception('[scaffold] - "No Provider for ' + type)
|
||||
assert 0, "No Provider for " + type
|
||||
|
||||
|
||||
class gocardless_provider:
|
||||
"""
|
||||
gocardless test account details 20-00-00, 55779911
|
||||
|
@ -29,20 +31,10 @@ class gocardless_provider:
|
|||
|
||||
def __init__(self):
|
||||
# gocardless are changing there api, not sure if we can switch yet
|
||||
self.client = gocardless.Client(
|
||||
self.client = gocardless_pro.Client(
|
||||
access_token=payment_providers['gocardless']['credentials']['access_token'],
|
||||
environment=payment_providers['gocardless']['environment'])
|
||||
|
||||
def subscribe_confirm(self, args):
|
||||
response = gocardless.client.confirm_resource(args)
|
||||
subscription = gocardless.client.subscription(args.get('resource_id'))
|
||||
return {
|
||||
'amount': subscription.amount,
|
||||
'start_date': subscription.created_at,
|
||||
'reference': subscription.id,
|
||||
'success': response.success
|
||||
}
|
||||
|
||||
def fetch_customers(self):
|
||||
"""Fetch list of customers payments"""
|
||||
for customer in self.client.customers.list().records:
|
||||
|
@ -78,11 +70,18 @@ class gocardless_provider:
|
|||
def get_token(self):
|
||||
return 'N/A'
|
||||
|
||||
def cancel_subscription(self, reference):
|
||||
def cancel_subscription(self, user, reference):
|
||||
try:
|
||||
subscription = gocardless.client.subscription(reference)
|
||||
response = subscription.cancel()
|
||||
except gocardless.exceptions.ClientError:
|
||||
subscription = self.client.subscriptions.get(reference)
|
||||
response = self.client.subscriptions.cancel(reference)
|
||||
except gocardless_pro.errors.InvalidApiUsageError as e:
|
||||
if e.code is 404:
|
||||
logger.info('Cancel subscription failed user not found %s %s' % (e.code, e))
|
||||
return {
|
||||
'success': False
|
||||
}
|
||||
except Exception as e:
|
||||
logger.info('Cancel subscription failed unknown reason code %s %s' % (e.code, e))
|
||||
return {
|
||||
'success': False
|
||||
}
|
||||
|
@ -90,7 +89,7 @@ class gocardless_provider:
|
|||
'amount': subscription.amount,
|
||||
'start_date': subscription.created_at,
|
||||
'reference': subscription.id,
|
||||
'success': response.get('success', False)
|
||||
'success': True if response.get('status_code') is '200' else False
|
||||
}
|
||||
|
||||
def create_subscription(self, user, session, amount,
|
||||
|
@ -115,18 +114,13 @@ class gocardless_provider:
|
|||
# response = self.client.redirect_flows.complete(r, params={
|
||||
# "session_token": session
|
||||
# })
|
||||
response = self.client.redirect_flows.get(r)
|
||||
response = self.client.redirect_flows.complete(r, params={'session_token': session})
|
||||
# response = self.client.redirect_flows.get(provider_response.get('redirect_flow_id'))
|
||||
|
||||
# response = gocardless.client.confirm_resource(provider_response)
|
||||
# subscription = gocardless.client.subscription(provider_response.get('resource_id'))
|
||||
user_id = response.links.customer
|
||||
mandate_id = response.links.mandate
|
||||
# user = subscription.user()
|
||||
user = self.client.customers.get(response.links.customer)
|
||||
mandate = self.client.mandates.get(response.links.mandate)
|
||||
logging.debug(user)
|
||||
logging.debug(mandate)
|
||||
|
||||
# for some reason go cardless is in pence, so 20.00 needs to be sent as 2000
|
||||
# what genious decided that was a good idea, now looks like i am charging £2000 :p
|
||||
|
@ -180,8 +174,8 @@ class braintree_provider:
|
|||
|
||||
def confirm_subscription(self, args):
|
||||
if self.provider == 'gocardless':
|
||||
response = gocardless.client.confirm_resource(args)
|
||||
subscription = gocardless.client.subscription(args.get('resource_id'))
|
||||
response = gocardless_pro.client.confirm_resource(args)
|
||||
subscription = gocardless_pro.client.subscription(args.get('resource_id'))
|
||||
return {
|
||||
'amount': subscription.amount,
|
||||
'start_date': subscription.created_at,
|
||||
|
@ -199,255 +193,3 @@ class braintree_provider:
|
|||
'reference': paying_member.reference,
|
||||
'amount': paying_member.amount}
|
||||
|
||||
|
||||
class payment:
|
||||
"""
|
||||
https://developer.gocardless.com/api-reference/#redirect-flows-create-a-redirect-flow
|
||||
paypal reference = https://github.com/paypal/PayPal-Python-SDK
|
||||
gocardless reference = https://github.com/paypal/PayPal-Python-SDK
|
||||
"""
|
||||
#~ def __call__(self, **args):
|
||||
#~ return self
|
||||
|
||||
def __init__(self, provider='gocardless', style='payment', mode='sandbox'):
|
||||
self.provider = provider
|
||||
self.environment = int(mode=='production')
|
||||
self.provider_id = PROVIDER_ID.get(provider)
|
||||
|
||||
print(payment_providers)
|
||||
if provider == 'paypal':
|
||||
paypal.configure(**payment_providers[provider]['credentials'])
|
||||
return
|
||||
|
||||
gocardless_pro.Client(
|
||||
access_token=payment_providers[provider]['credentials']['access_token'],
|
||||
environment=payment_providers[provider])
|
||||
#~ environment = int('production' = payment_providers[provider]['environment'])
|
||||
gocardless.environment = payment_providers[provider]['environment']
|
||||
gocardless.set_details(**payment_providers[provider]['credentials'])
|
||||
merchant = gocardless.client.merchant()
|
||||
|
||||
def lookup_provider_by_id(self, provider_id):
|
||||
return PROVIDER_NAME.get(provider_id, None)
|
||||
|
||||
def make_donation(self, amount, reference, redirect_success, redirect_failure):
|
||||
if self.provider == 'paypal':
|
||||
payment = paypal.Payment({
|
||||
"intent": "sale",
|
||||
"payer": {"payment_method": "paypal"},
|
||||
"redirect_urls": {
|
||||
"return_url": redirect_success,
|
||||
"cancel_url": redirect_failure},
|
||||
|
||||
"transactions": [{
|
||||
"amount": {
|
||||
"total": amount,
|
||||
"currency": "GBP"},
|
||||
"description": reference}]})
|
||||
|
||||
payment_response = payment.create()
|
||||
print('payment create')
|
||||
if payment_response:
|
||||
print(payment_response)
|
||||
for link in payment.links:
|
||||
if link.method == "REDIRECT":
|
||||
redirect_url = str(link.href)
|
||||
print(redirect_url)
|
||||
return str(redirect_url)
|
||||
else:
|
||||
print("Error while creating payment:")
|
||||
print(payment.error)
|
||||
|
||||
if self.provider == 'gocardless':
|
||||
return gocardless.client.new_bill_url(
|
||||
float(amount),
|
||||
name=reference,
|
||||
redirect_uri=redirect_success)
|
||||
|
||||
return 'Error something went wrong'
|
||||
|
||||
def fetch_subscriptions(self):
|
||||
if self.provider == 'gocardless':
|
||||
merchant = gocardless.client.merchant()
|
||||
for paying_member in merchant.subscriptions():
|
||||
user=paying_member.user()
|
||||
print(dir(paying_member))
|
||||
print(paying_member.next_interval_start)
|
||||
print(paying_member.status)
|
||||
print(dir(paying_member.user()))
|
||||
yield {
|
||||
'email': user.email,
|
||||
'start_date': paying_member.created_at,
|
||||
'reference': paying_member.id,
|
||||
'amount': paying_member.amount}
|
||||
|
||||
|
||||
def confirm_subscription(self, args):
|
||||
if self.provider == 'gocardless':
|
||||
response = gocardless.client.confirm_resource(args)
|
||||
subscription = gocardless.client.subscription(args.get('resource_id'))
|
||||
return {
|
||||
'amount': subscription.amount,
|
||||
'start_date': subscription.created_at,
|
||||
'reference': subscription.id
|
||||
}
|
||||
|
||||
if self.provider == 'paypal':
|
||||
print('subscribe_confirm')
|
||||
payment_token = args.get('token', '')
|
||||
billing_agreement_response = paypal.BillingAgreement.execute(payment_token)
|
||||
amount = 0
|
||||
print(billing_agreement_response)
|
||||
print(billing_agreement_response.id)
|
||||
for row in billing_agreement_response.plan.payment_definitions:
|
||||
amount = row.amount.value
|
||||
|
||||
return {
|
||||
'amount': amount,
|
||||
'start_date': billing_agreement_response.start_date,
|
||||
'reference': billing_agreement_response.id
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def unsubscribe(self, reference):
|
||||
if self.provider == 'gocardless':
|
||||
print('unsubscribe gocardless')
|
||||
subscription = gocardless.client.subscription(reference)
|
||||
print(subscription.cancel())
|
||||
|
||||
if self.provider == 'paypal':
|
||||
# this may be wrong
|
||||
# ManageRecurringPaymentsProfileStatus
|
||||
print(reference)
|
||||
billing_plan = paypal.BillingAgreement.find(reference)
|
||||
print(billing_plan)
|
||||
print(billing_plan.error)
|
||||
#~ billing_plan.replace([{"op": "replace","path": "/","value": {"state":"DELETED"}}])
|
||||
print(billing_plan.error)
|
||||
#~ invoice = paypal.Invoice.find(reference)
|
||||
options = {
|
||||
"subject": "Cancelling membership",
|
||||
"note": "Canceling invoice",
|
||||
"send_to_merchant": True,
|
||||
"send_to_payer": True
|
||||
}
|
||||
|
||||
if billing_plan.cancel(options): # return True or False
|
||||
print("Invoice[%s] cancel successfully" % (invoice.id))
|
||||
else:
|
||||
print(billing_plan.error)
|
||||
|
||||
|
||||
def subscribe(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'):
|
||||
if self.provider == 'gocardless':
|
||||
return gocardless.client.new_subscription_url(
|
||||
amount=float(amount),
|
||||
interval_length=interval_length,
|
||||
interval_unit=interval_unit,
|
||||
name=name,
|
||||
redirect_uri=redirect_success)
|
||||
|
||||
if self.provider == 'paypal':
|
||||
billing_plan = paypal.BillingPlan({
|
||||
"name": name,
|
||||
"description": "Membership subscription",
|
||||
"merchant_preferences": {
|
||||
"auto_bill_amount": "yes",
|
||||
"cancel_url": redirect_failure,
|
||||
"initial_fail_amount_action": "continue",
|
||||
"max_fail_attempts": "1",
|
||||
"return_url": redirect_success,
|
||||
"setup_fee": {
|
||||
"currency": "GBP",
|
||||
"value": amount
|
||||
}
|
||||
},
|
||||
"payment_definitions": [{
|
||||
"amount": {
|
||||
"currency": "GBP",
|
||||
"value": amount
|
||||
},
|
||||
"cycles": "0",
|
||||
"frequency": interval_unit,
|
||||
"frequency_interval": interval_length,
|
||||
"name": "Regular 1",
|
||||
"type": "REGULAR"
|
||||
}
|
||||
],
|
||||
"type": "INFINITE"
|
||||
})
|
||||
print('create bill')
|
||||
|
||||
response = billing_plan.create()
|
||||
|
||||
billing_plan = paypal.BillingPlan.find(billing_plan.id)
|
||||
|
||||
if billing_plan.activate():
|
||||
start_date = datetime.utcnow() + timedelta(minutes=10)
|
||||
billing_agreement = paypal.BillingAgreement({
|
||||
"name": billing_plan.name,
|
||||
"description": name,
|
||||
"start_date": start_date.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
||||
"plan": {"id": str(billing_plan.id)},
|
||||
"payer": {"payment_method": "paypal"}
|
||||
})
|
||||
|
||||
if billing_agreement.create():
|
||||
print('billing agreement id')
|
||||
print(billing_agreement.id)
|
||||
|
||||
for link in billing_agreement.links:
|
||||
if link.rel == "approval_url":
|
||||
approval_url = link.href
|
||||
return approval_url
|
||||
else:
|
||||
print(billing_agreement.error)
|
||||
print('failed')
|
||||
|
||||
def confirm(self, args):
|
||||
confirm_details = {}
|
||||
confirm_details['successfull'] = False
|
||||
print('---------------------')
|
||||
print(args)
|
||||
|
||||
from pprint import pprint
|
||||
if self.provider == 'paypal':
|
||||
print(args.get('paymentId'))
|
||||
print(args.get('PayerID'))
|
||||
payment = paypal.Payment.find(args.get('paymentId'))
|
||||
pprint(payment)
|
||||
print(pprint(payment))
|
||||
print(payment)
|
||||
|
||||
confirm_details['name'] = payment['payer']['payer_info'].first_name + ' ' + payment['payer']['payer_info'].last_name
|
||||
confirm_details['user'] = payment['payer']['payer_info'].email
|
||||
confirm_details['status'] = payment.state
|
||||
confirm_details['amount'] = payment['transactions'][0]['amount'].total
|
||||
confirm_details['created'] = payment.create_time
|
||||
confirm_details['reference'] = payment.id
|
||||
pprint(confirm_details)
|
||||
|
||||
|
||||
if payment.execute({"payer_id": args.get('PayerID')}): # return True or False
|
||||
confirm_details['successfull'] = True
|
||||
print("Payment[%s] execute successfully" % (args.get('paymentId')))
|
||||
else:
|
||||
print(payment.error)
|
||||
return confirm_details
|
||||
|
||||
if self.provider == 'gocardless':
|
||||
bill_id = args.get('resource_id')
|
||||
gocardless.client.confirm_resource(args)
|
||||
if bill_id:
|
||||
bill = gocardless.client.bill(bill_id)
|
||||
confirm_details['name'] = bill.name
|
||||
confirm_details['user'] = bill.user
|
||||
confirm_details['status'] = bill.status
|
||||
confirm_details['amount'] = bill.amount
|
||||
#~ confirm_details['amount_minus_fees'] = bill.amount_minus_fees
|
||||
confirm_details['created'] = bill.created_at
|
||||
confirm_details['reference'] = bill_id
|
||||
confirm_details['successfull'] = True
|
||||
return confirm_details
|
||||
return None
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
from test_plus.test import TestCase
|
||||
from mock import patch, Mock, MagicMock, PropertyMock
|
||||
from mhackspace.users.models import Membership
|
||||
import django.utils.timezone
|
||||
from collections import namedtuple
|
||||
|
||||
from mhackspace.subscriptions.payments import gocardless_provider
|
||||
|
||||
|
||||
class gocardlessMocks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.date_now = django.utils.timezone.now()
|
||||
self.user = self.make_user()
|
||||
self.auth_gocardless()
|
||||
|
||||
def create_membership_record(self):
|
||||
member = Membership()
|
||||
member.user = self.user
|
||||
member.payment = '20.00'
|
||||
member.date = self.date_now
|
||||
member.save()
|
||||
return member
|
||||
|
||||
@patch('mhackspace.subscriptions.payments.gocardless_pro', autospec=True)
|
||||
def auth_gocardless(self, mock_request):
|
||||
RedirectFlow = namedtuple('RedirectFlow', 'links')
|
||||
Links = namedtuple('Links', 'mandate, customer')
|
||||
mp = RedirectFlow(
|
||||
links=Links(mandate='02', customer='01'))
|
||||
|
||||
self.provider = gocardless_provider()
|
||||
self.provider.client.redirect_flows.get = PropertyMock(return_value=mp)
|
||||
|
||||
return self.provider
|
||||
|
||||
def mock_success_responses(self):
|
||||
subscription_properties = Mock(
|
||||
id='02',
|
||||
status='active',
|
||||
amount=20.00,
|
||||
created_at='date'
|
||||
)
|
||||
|
||||
mock_list = MagicMock()
|
||||
mock_list_records = MagicMock(side_effect=[subscription_properties])
|
||||
mock_list.records.return_value = mock_list_records
|
||||
|
||||
self.provider.client.subscriptions.list = mock_list
|
||||
ApiResponse = namedtuple('ApiResponse', 'api_response, created_at')
|
||||
ApiResponseStatus = namedtuple('ApiResponseStatus', 'status_code')
|
||||
|
||||
self.provider.client.subscriptions.create = Mock(
|
||||
return_value=ApiResponse(
|
||||
created_at=self.date_now,
|
||||
api_response=ApiResponseStatus(status_code='200'))
|
||||
)
|
||||
|
||||
self.provider.client.subscriptions.get = Mock(
|
||||
return_value=subscription_properties)
|
||||
|
||||
self.provider.client.subscriptions.cancel = PropertyMock(
|
||||
return_value={'status_code': '200'})
|
||||
|
|
@ -2,57 +2,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from test_plus.test import TestCase
|
||||
from unittest import skip
|
||||
from mock import patch, Mock
|
||||
from mock import patch, Mock, MagicMock
|
||||
from mhackspace.users.models import Membership
|
||||
import django.utils.timezone
|
||||
|
||||
from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider
|
||||
from mhackspace.subscriptions.payments import gocardless_provider, braintree_provider
|
||||
from mhackspace.subscriptions.tests.mocks import gocardlessMocks
|
||||
|
||||
class TestPaymentGatewaysGocardless(TestCase):
|
||||
|
||||
class TestPaymentGatewaysGocardless(gocardlessMocks):
|
||||
|
||||
def setUp(self):
|
||||
self.auth_gocardless()
|
||||
super().setUp()
|
||||
|
||||
@patch('mhackspace.subscriptions.payments.gocardless.request.requests.get', autospec=True)
|
||||
def auth_gocardless(self, mock_request):
|
||||
# mock braintree initalisation request
|
||||
mock_request.return_value = Mock(ok=True)
|
||||
mock_request.return_value.json.return_value = {
|
||||
"id": "1",
|
||||
"created_at": "2011-11-18T17:07:09Z",
|
||||
"access_token": "test_token",
|
||||
"next_payout_date": "2011-11-18T17:07:09Z"
|
||||
}
|
||||
def test_unsubscribe(self):
|
||||
self.mock_success_responses()
|
||||
|
||||
with patch('gocardless.resources.Merchant') as mock_subscription:
|
||||
self.provider = gocardless_provider()
|
||||
return self.provider #self.provider
|
||||
|
||||
@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,
|
||||
created_at='date'
|
||||
)
|
||||
result = self.provider.cancel_subscription(reference='M01')
|
||||
result = self.provider.cancel_subscription(user=self.user, 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')
|
||||
self.assertEqual(result.get('reference'), '02')
|
||||
self.assertEqual(result.get('success'), 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'
|
||||
)
|
||||
def test_confirm_subscription_callback(self):
|
||||
self.mock_success_responses()
|
||||
membership = self.create_membership_record()
|
||||
|
||||
request_params = {
|
||||
'resource_uri': 'http://gocardless/resource/url/01',
|
||||
|
@ -62,66 +36,23 @@ class TestPaymentGatewaysGocardless(TestCase):
|
|||
'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')
|
||||
# membership = Membership.objects.get(user=self.user)
|
||||
result = self.provider.confirm_subscription(
|
||||
membership=membership,
|
||||
session=None,
|
||||
provider_response={'redirect_flow_id': 'redirect_mock_url'},
|
||||
name='test')
|
||||
|
||||
self.assertEqual(result.get('amount'), '20.00')
|
||||
self.assertEqual(result.get('reference'), '02')
|
||||
self.assertEqual(result.get('success'), '200')
|
||||
|
||||
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=[item])
|
||||
|
||||
# mock out gocardless subscriptions method, and return our own values
|
||||
self.mock_success_responses()
|
||||
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'), '01')
|
||||
self.assertEqual(item.get('start_date'), 'date')
|
||||
self.assertEqual(item.get('reference'), '02')
|
||||
self.assertEqual(item.get('amount'), 20.00)
|
||||
|
||||
|
||||
class DisabledestPaymentGatewaysBraintree(TestCase):
|
||||
@patch('mhackspace.subscriptions.payments.braintree.Configuration.configure')
|
||||
def auth_braintree(self, mock_request):
|
||||
# mock braintree initalisation request
|
||||
mock_request.return_value = Mock(ok=True)
|
||||
mock_request.return_value.json.return_value = {
|
||||
"id": "1",
|
||||
"created_at": "2011-11-18T17:07:09Z",
|
||||
"access_token": "test_token",
|
||||
"next_payout_date": "2011-11-18T17:07:09Z"
|
||||
}
|
||||
|
||||
self.provider = braintree_provider()
|
||||
|
||||
@patch('mhackspace.subscriptions.payments.braintree.Subscription.search')
|
||||
def test_fetch_subscription_braintree(self, mock_request):
|
||||
provider = self.auth_braintree()
|
||||
|
||||
items = [Mock(
|
||||
id='01',
|
||||
status='active',
|
||||
amount=20.00,
|
||||
reference='ref01',
|
||||
created_at='date'
|
||||
)]
|
||||
items[-1].user.return_value = Mock(email='test@test.com')
|
||||
|
||||
mock_request.return_value = items
|
||||
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('start_date'), 'date')
|
||||
self.assertEqual(item.get('amount'), 20.00)
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||
# from django.contrib.auth.models import Group
|
||||
from django.test import Client
|
||||
from django.test import RequestFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
from test_plus.test import TestCase
|
||||
from mock import patch, Mock
|
||||
from mhackspace.users.models import Membership
|
||||
from mhackspace.users.models import Membership
|
||||
from mhackspace.users.models import User
|
||||
|
||||
from mhackspace.subscriptions.payments import gocardless_provider
|
||||
from mhackspace.subscriptions.tests.mocks import gocardlessMocks
|
||||
|
||||
from ..views import (
|
||||
MembershipCancelView,
|
||||
|
@ -15,53 +19,72 @@ from ..views import (
|
|||
)
|
||||
|
||||
|
||||
class BaseUserTestCase(TestCase):
|
||||
class BaseUserTestCase(gocardlessMocks):
|
||||
fixtures = ['groups']
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
super().setUp()
|
||||
# self.user = self.make_user()
|
||||
# self.user.save()
|
||||
self.factory = RequestFactory()
|
||||
self.client = Client()
|
||||
self.client.login(
|
||||
username=self.user.username,
|
||||
password=self.user.password)
|
||||
|
||||
|
||||
class TestSubscriptionSuccessRedirectView(BaseUserTestCase):
|
||||
@patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
|
||||
@patch('mhackspace.subscriptions.views.select_provider', autospec=True)
|
||||
def test_success_redirect_url(self, mock_subscription, mock_provider):
|
||||
mock_subscription.return_value = mock_provider
|
||||
mock_provider.confirm_subscription.return_value = {
|
||||
'amount': 20.00,
|
||||
'start_date': '2017-01-01T17:07:09Z',
|
||||
'reference': 'MH0001',
|
||||
'email': 'user@test.com',
|
||||
'success': True
|
||||
}
|
||||
# @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
|
||||
# @patch('mhackspace.subscriptions.views.select_provider', autospec=True)
|
||||
def test_success_redirect_url(self):
|
||||
self.mock_success_responses()
|
||||
self.create_membership_record()
|
||||
# mock_gocardless.subscriptions.create.return_value = 'temp'
|
||||
# mock_provider.confirm_subscription.return_value = {
|
||||
# 'amount': 20.00,
|
||||
# 'start_date': '2017-01-01T17:07:09Z',
|
||||
# 'reference': 'MH0001',
|
||||
# 'email': 'user@test.com',
|
||||
# 'success': True
|
||||
# }
|
||||
|
||||
request = self.factory.post(
|
||||
response = self.client.post(
|
||||
reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), {
|
||||
'resource_id': 'R01',
|
||||
'resource_type': 'subscription',
|
||||
'resource_url': 'https://sandbox.gocardless.com',
|
||||
'signature': 'test_signature'
|
||||
}
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
|
||||
setattr(request, 'session', 'session')
|
||||
messages = FallbackStorage(request)
|
||||
setattr(request, '_messages', messages)
|
||||
request.user = self.user
|
||||
# print('=============================')
|
||||
# setattr(request, 'session', 'session')
|
||||
# messages = FallbackStorage(request)
|
||||
# setattr(request, '_messages', messages)
|
||||
# request.user = user1
|
||||
|
||||
view = MembershipJoinSuccessView()
|
||||
view.request = request
|
||||
self.assertEqual(
|
||||
view.get_redirect_url(provider ='gocardless'),
|
||||
reverse('users:detail', kwargs={'username': self.user.username})
|
||||
)
|
||||
# view = MembershipJoinSuccessView()
|
||||
# view.request = request
|
||||
# print(self.user)
|
||||
self.assertRedirects(
|
||||
response,
|
||||
expected_url='/accounts/login/?next=/membership/gocardless/success',
|
||||
status_code=302,
|
||||
target_status_code=200)
|
||||
# self.assertEqual(
|
||||
# view.get_redirect_url(provider ='gocardless'),
|
||||
# reverse('users:detail', kwargs={'username': self.user.username})
|
||||
# )
|
||||
|
||||
# view = Memhttps://www.youtube.com/bershipJoinSuccessView()
|
||||
# view.request = request
|
||||
members = Membership.objects.all()
|
||||
self.assertEqual(members.count(), 1)
|
||||
|
||||
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
|
||||
def test_failure_redirect_url(self, mock_obj):
|
||||
# @patch('mhackspace.subscriptions.payments.gocardless_pro.client.subscriptions', autospec=True)
|
||||
def test_failure_redirect_url(self):
|
||||
self.mock_success_responses()
|
||||
# Instantiate the view directly. Never do this outside a test!
|
||||
# Generate a fake request
|
||||
request = self.factory.post(
|
||||
|
|
|
@ -14,7 +14,7 @@ from mhackspace.users.models import User, Membership
|
|||
from mhackspace.users.models import MEMBERSHIP_CANCELLED
|
||||
from mhackspace.users.forms import MembershipJoinForm
|
||||
from mhackspace.subscriptions.payments import select_provider
|
||||
from mhackspace.subscriptions.helper import create_or_update_membership
|
||||
from mhackspace.subscriptions.helper import create_or_update_membership, cancel_membership
|
||||
|
||||
|
||||
class MembershipCancelView(LoginRequiredMixin, RedirectView):
|
||||
|
@ -28,17 +28,12 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView):
|
|||
member = Membership.objects.filter(user=self.request.user).first()
|
||||
|
||||
result = provider.cancel_subscription(
|
||||
user=self.request.user,
|
||||
reference=member.reference
|
||||
)
|
||||
if result.get('success') is True:
|
||||
# set membership to cancelled on success
|
||||
member.status = MEMBERSHIP_CANCELLED
|
||||
member.save()
|
||||
|
||||
|
||||
# remove user from group on success
|
||||
group = Group.objects.get(name='members')
|
||||
self.request.user.groups.remove(group)
|
||||
# if result.get('success') is True:
|
||||
cancel_membership(user=self.request.user)
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.SUCCESS,
|
||||
|
@ -68,7 +63,7 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView):
|
|||
result = {
|
||||
'email': self.request.user.email,
|
||||
'reference': user_code,
|
||||
'amount': form_subscription.cleaned_data.get('amount', 20.00) * 0.01,
|
||||
'amount': form_subscription.cleaned_data.get('amount', 20.00),
|
||||
'start_date': timezone.now()
|
||||
}
|
||||
|
||||
|
@ -99,9 +94,10 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
|
|||
def get_redirect_url(self, *args, **kwargs):
|
||||
payment_provider = 'gocardless'
|
||||
provider = select_provider(payment_provider)
|
||||
print(self.request.user)
|
||||
membership = Membership.objects.get(user=self.request.user)
|
||||
|
||||
name="Membership your membership id is MH%s" % membership.reference
|
||||
name = "Membership your membership id is MH%s" % membership.reference
|
||||
result = provider.confirm_subscription(
|
||||
membership=membership,
|
||||
session=self.request.session.session_key,
|
||||
|
@ -110,6 +106,8 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
|
|||
)
|
||||
|
||||
# if something went wrong return to profile with an error
|
||||
|
||||
result['start_date'] = timezone.now()
|
||||
if result.get('success') is False:
|
||||
messages.add_message(
|
||||
self.request,
|
||||
|
|
|
@ -168,6 +168,7 @@
|
|||
<div class="container">
|
||||
{% block footer-left %}{% endblock footer-left %}
|
||||
<span class="text-muted">© {% now "Y" %} Maidstone Hackspace</span>
|
||||
<span><a href="https://maidstone-hackspace.org.uk/wiki/maidstone-hackspace-constitution/">Constitution</a></span>
|
||||
<div class="float-right">
|
||||
<a href="https://github.com/maidstone-hackspace/" target="_blank"><i class="fa fa-github"></i></a>
|
||||
<a href="https://github.com/maidstone-hackspace/" target="_blank"><i class="fa fa-twitter"></i></a>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ user.username }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ user.username }}</h1>
|
||||
<form class="form-horizontal" method="post" action="{% url 'users:update' %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
{{ form_blurb|crispy }}
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button type="submit" class="btn">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -34,7 +34,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% if membership.get_status %}
|
||||
{% if membership.is_active %}
|
||||
<div id="membercard" class="registered">
|
||||
<div class="date">Joined {{membership.date}}</div>
|
||||
<div class="container">
|
||||
|
@ -74,6 +74,7 @@
|
|||
<div class="col-sm-12">
|
||||
<p>
|
||||
<a class="btn btn-primary" href="{% url 'users:update' %}" role="button">My Info</a>
|
||||
<a class="btn btn-primary" href="{% url 'users:access_cards' %}" role="button">My Rfid Cards</a>
|
||||
<a class="btn btn-primary" href="{% url 'account_email' %}" role="button">E-Mail</a>
|
||||
</p>
|
||||
<!-- Your Stuff: Custom user template urls -->
|
||||
|
|
|
@ -9,9 +9,10 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
|||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
from django.conf.urls import url
|
||||
from .models import User, Membership, MEMBERSHIP_STATUS_CHOICES
|
||||
from .models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
|
||||
|
||||
from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
|
||||
# from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
|
||||
from mhackspace.users.tasks import update_users_memebership_status
|
||||
|
||||
|
||||
class MyUserChangeForm(UserChangeForm):
|
||||
|
@ -46,21 +47,27 @@ class MyUserAdmin(AuthUserAdmin):
|
|||
list_display = ('username', 'name', 'is_superuser')
|
||||
search_fields = ['name']
|
||||
|
||||
|
||||
@admin.register(Membership)
|
||||
class MembershipAdmin(ModelAdmin):
|
||||
list_display = ('user_id', 'email', 'payment', 'date', 'status')
|
||||
list_filter = ('status',)
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(MyUserAdmin, self).get_urls()
|
||||
urls = super(MembershipAdmin, self).get_urls()
|
||||
my_urls = [
|
||||
url(r'^refresh/payments/$', self.admin_site.admin_view(self.refresh_payments))
|
||||
]
|
||||
return my_urls + urls
|
||||
|
||||
def refresh_payments(self, request):
|
||||
for user in update_subscriptions(provider_name='gocardless'):
|
||||
continue
|
||||
self.message_user(request, 'Successfully imported refresh users payment status')
|
||||
return HttpResponseRedirect(reverse('admin:feeds_article_changelist'))
|
||||
update_users_memebership_status()
|
||||
# for user in update_subscriptions(provider_name='gocardless'):
|
||||
# continue
|
||||
self.message_user(request, 'Successfully triggered user payment refresh')
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
|
||||
@admin.register(Membership)
|
||||
class MembershipAdmin(ModelAdmin):
|
||||
list_display = ('user', 'payment', 'date', 'status')
|
||||
list_filter = ('status',)
|
||||
@admin.register(Rfid)
|
||||
class RfidAdmin(ModelAdmin):
|
||||
list_display = ('code', 'description')
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-04-27 18:25
|
||||
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', '0003_merge_20170226_0844'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Rfid',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.PositiveIntegerField()),
|
||||
('description', models.CharField(blank=True, max_length=255, verbose_name='Short rfid description')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-12 20:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0004_auto_20170813_1557'),
|
||||
('users', '0004_rfid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-13 07:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0004_auto_20170813_1557'),
|
||||
('users', '0004_rfid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-13 17:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_merge_20170912_2037'),
|
||||
('users', '0005_merge_20170913_0740'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-14 20:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0006_merge_20170913_1718'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='rfid',
|
||||
name='code',
|
||||
field=models.CharField(max_length=7),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-17 09:48
|
||||
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', '0007_auto_20170914_2021'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='membership',
|
||||
name='email',
|
||||
field=models.CharField(max_length=255, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='membership',
|
||||
name='user',
|
||||
field=models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
|
@ -45,44 +45,54 @@ class Blurb(models.Model):
|
|||
skills = models.CharField(max_length=255)
|
||||
description = models.TextField()
|
||||
|
||||
MEMBERSHIP_CANCELLED = 0
|
||||
MEMBERSHIP_ACTIVE = 1
|
||||
MEMBERSHIP_CANCELLED = 4
|
||||
|
||||
MEMBERSHIP_STATUS_CHOICES = (
|
||||
(0, 'Guest user'),
|
||||
(1, 'Active membership'),
|
||||
(MEMBERSHIP_ACTIVE, 'Active membership'),
|
||||
(3, 'Membership Expired'),
|
||||
(4, 'Membership Cancelled')
|
||||
(MEMBERSHIP_CANCELLED, 'Membership Cancelled')
|
||||
)
|
||||
|
||||
MEMBERSHIP_STRING = {
|
||||
0: 'Guest user',
|
||||
1: 'Active membership',
|
||||
3: 'Membership Expired'
|
||||
MEMBERSHIP_ACTIVE: 'Active membership',
|
||||
3: 'Membership Expired',
|
||||
MEMBERSHIP_CANCELLED: 'Membership Cancelled'
|
||||
}
|
||||
|
||||
MEMBERSHIP_STATUS = {
|
||||
'signup': 0, # This means the user has not completed signup
|
||||
'active': 1,
|
||||
'cancelled': 2
|
||||
'active': MEMBERSHIP_ACTIVE,
|
||||
'expired': 3,
|
||||
'cancelled': MEMBERSHIP_CANCELLED
|
||||
}
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
user = models.ForeignKey(
|
||||
user = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True, blank=True,
|
||||
default=None,
|
||||
related_name='user'
|
||||
related_name='user',
|
||||
unique=True
|
||||
)
|
||||
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, choices=MEMBERSHIP_STATUS_CHOICES)
|
||||
email = models.CharField(max_length=255)
|
||||
email = models.CharField(max_length=255, unique=True)
|
||||
|
||||
@property
|
||||
def get_status(self):
|
||||
return MEMBERSHIP_STRING[self.status]
|
||||
|
||||
def is_active(self):
|
||||
if self.status is MEMBERSHIP_ACTIVE:
|
||||
return True
|
||||
return False
|
||||
|
||||
def lookup_status(name):
|
||||
if not name:
|
||||
return 0
|
||||
|
@ -90,3 +100,20 @@ class Membership(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.reference
|
||||
|
||||
|
||||
# users rfid card to user mapping, user can have more than one card
|
||||
class Rfid(models.Model):
|
||||
code = models.CharField(max_length=7)
|
||||
description = models.CharField(_('Short rfid description'), blank=True, max_length=255)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True, blank=True,
|
||||
# related_name='rfid_user'
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
def name(self):
|
||||
return self.user.name
|
||||
|
|
|
@ -4,5 +4,4 @@ from mhackspace.subscriptions.management.commands.update_membership_status impor
|
|||
|
||||
@shared_task
|
||||
def update_users_memebership_status():
|
||||
for user in update_subscriptions(provider_name='gocardless'):
|
||||
continue
|
||||
update_subscriptions(provider_name='gocardless')
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
from test_plus.test import TestCase
|
||||
from django.db import models
|
||||
from allauth.utils import serialize_instance
|
||||
|
||||
|
||||
class TestSerializeUser(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
|
||||
def test_serialize(self):
|
||||
"""check we can serialize the user object for allauth, custom types can break it"""
|
||||
result = serialize_instance(self.user)
|
||||
self.assertTrue(
|
||||
isinstance(result, dict),
|
||||
)
|
|
@ -26,4 +26,9 @@ urlpatterns = [
|
|||
view=views.UserUpdateView.as_view(),
|
||||
name='update'
|
||||
),
|
||||
url(
|
||||
regex=r'^-access-cards$',
|
||||
view=views.RfidCardsUpdateView.as_view(),
|
||||
name='access_cards'
|
||||
),
|
||||
]
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
|
||||
from django.views.generic import DetailView, ListView, RedirectView, UpdateView, CreateView
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from .models import Rfid
|
||||
from .models import User
|
||||
from .models import Blurb
|
||||
from .models import Membership
|
||||
|
@ -65,6 +66,18 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
|
|||
|
||||
return super(UserUpdateView, self).form_valid(form)
|
||||
|
||||
|
||||
class RfidCardsUpdateView(LoginRequiredMixin, CreateView):
|
||||
fields = ['user', 'code', 'description', ]
|
||||
model = Rfid
|
||||
|
||||
def form_valid(self, form):
|
||||
user = self.request.user
|
||||
form.instance.user = user
|
||||
return super(RfidCardsUpdateView, self).form_valid(form)
|
||||
|
||||
|
||||
|
||||
class UserListView(LoginRequiredMixin, ListView):
|
||||
model = User
|
||||
# These next two lines tell the view to index lookups by username
|
||||
|
|
|
@ -61,7 +61,10 @@ git+https://github.com/olymk2/scaffold.git
|
|||
git+git://github.com/django-wiki/django-wiki.git
|
||||
|
||||
djangorestframework==3.6.3
|
||||
djangorestframework-jwt
|
||||
django-filter==1.0.2
|
||||
coreapi
|
||||
# api libraries end
|
||||
|
||||
draceditor==1.1.8
|
||||
# django-spirit
|
||||
|
|
Loading…
Reference in New Issue