Merge from upstream/master
This commit is contained in:
commit
de4f11878b
|
@ -15,7 +15,7 @@ pipeline:
|
||||||
- cp -n env.example .env
|
- cp -n env.example .env
|
||||||
- mkdir -p ./cache/packages ./cache/pip
|
- mkdir -p ./cache/packages ./cache/pip
|
||||||
- pip install --user --cache-dir ./cache/pip -r ./requirements/test.txt
|
- 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:
|
publish-test:
|
||||||
pull: True
|
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.
|
Repository for the maidstone hackspace website, feel free to fork this site for your own Hackspace.
|
||||||
|
|
||||||
|
|
||||||
** Requirements
|
** Requirements
|
||||||
Before getting started make sure you have git, docker and docker-compose installed on your machine.
|
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
|
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
|
docker-compose -f dev.yml run --rm django python manage.py migrate
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
*** Create the admin user.
|
*** 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
|
#+BEGIN_SRC sh
|
||||||
docker-compose -f dev.yml run --rm django python manage.py createsuperuser
|
docker-compose -f dev.yml run --rm django python manage.py createsuperuser
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
@ -74,3 +73,24 @@ docker-compose -fdev.yml run --rm django python manage.py list_subscriptions
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
docker-compose -fdev.yml run --rm django python manage.py rendervariations 'blog.Post.image' --replace
|
docker-compose -fdev.yml run --rm django python manage.py rendervariations 'blog.Post.image' --replace
|
||||||
#+END_SRC
|
#+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
|
#!/bin/sh
|
||||||
python manage.py migrate
|
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',
|
'whitenoise.runserver_nostatic',
|
||||||
'stdimage',
|
'stdimage',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'django_filters',
|
||||||
'draceditor',
|
'draceditor',
|
||||||
'haystack',
|
'haystack',
|
||||||
'djconfig',
|
'djconfig',
|
||||||
|
@ -140,7 +141,7 @@ LOCAL_APPS = (
|
||||||
'mhackspace.blog',
|
'mhackspace.blog',
|
||||||
'mhackspace.core',
|
'mhackspace.core',
|
||||||
'mhackspace.requests',
|
'mhackspace.requests',
|
||||||
'mhackspace.register',
|
'mhackspace.rfid',
|
||||||
)
|
)
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# 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://':
|
#if CELERY_BROKER_URL == 'django://':
|
||||||
# CELERY_RESULT_BACKEND = 'redis://'
|
# CELERY_RESULT_BACKEND = 'redis://'
|
||||||
#else:
|
#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'
|
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
||||||
INSTALLED_APPS += ('django_celery_results','django_celery_beat',)
|
INSTALLED_APPS += ('django_celery_results','django_celery_beat',)
|
||||||
CELERY_TIMEZONE = 'UTC'
|
CELERY_TIMEZONE = 'UTC'
|
||||||
|
@ -449,10 +455,12 @@ REST_FRAMEWORK = {
|
||||||
'rest_framework.filters.OrderingFilter'
|
'rest_framework.filters.OrderingFilter'
|
||||||
),
|
),
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
|
# 'rest_framework.permissions.IsAuthenticated',
|
||||||
|
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
|
||||||
),
|
),
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
|
||||||
|
# 'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
),
|
),
|
||||||
|
@ -472,3 +480,4 @@ MATRIX_USER=env('MATRIX_USERNAME')
|
||||||
MATRIX_PASSWORD=env('MATRIX_PASSWORD')
|
MATRIX_PASSWORD=env('MATRIX_PASSWORD')
|
||||||
MATRIX_ROOM=env('MATRIX_ROOM')
|
MATRIX_ROOM=env('MATRIX_ROOM')
|
||||||
MSG_PREFIX = 'MH'
|
MSG_PREFIX = 'MH'
|
||||||
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
|
@ -86,7 +86,7 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
||||||
|
|
||||||
########## CELERY
|
########## CELERY
|
||||||
# In development, all tasks will be executed locally by blocking until the task returns
|
# In development, all tasks will be executed locally by blocking until the task returns
|
||||||
CELERY_ALWAYS_EAGER = True
|
# CELERY_ALWAYS_EAGER = True
|
||||||
########## END CELERY
|
########## END CELERY
|
||||||
|
|
||||||
# Your local stuff: Below this line define 3rd party library settings
|
# Your local stuff: Below this line define 3rd party library settings
|
||||||
|
@ -99,5 +99,50 @@ CAPTCHA = {
|
||||||
WHITENOISE_AUTOREFRESH = True
|
WHITENOISE_AUTOREFRESH = True
|
||||||
WHITENOISE_USE_FINDERS = 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'
|
PAYMENT_PROVIDERS['gocardless']['redirect_url'] = 'http://127.0.0.1:8180'
|
||||||
TEMPLATE_DEBUG = False
|
TEMPLATE_DEBUG = False
|
||||||
|
|
|
@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = True
|
||||||
#disabledd so csrf works with ajax
|
#disabledd so csrf works with ajax
|
||||||
CSRF_COOKIE_HTTPONLY = False
|
CSRF_COOKIE_HTTPONLY = False
|
||||||
X_FRAME_OPTIONS = 'DENY'
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
||||||
# SITE CONFIGURATION
|
# SITE CONFIGURATION
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = True
|
||||||
#disabledd so csrf works with ajax
|
#disabledd so csrf works with ajax
|
||||||
CSRF_COOKIE_HTTPONLY = False
|
CSRF_COOKIE_HTTPONLY = False
|
||||||
X_FRAME_OPTIONS = 'DENY'
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
||||||
# SITE CONFIGURATION
|
# SITE CONFIGURATION
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -67,5 +67,8 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
DATABASES = {
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.static import static
|
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.auth import views as auth_views
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from rest_framework.documentation import include_docs_urls
|
||||||
|
|
||||||
from mhackspace.contact.views import contact
|
from mhackspace.contact.views import contact
|
||||||
from mhackspace.members.views import MemberListView
|
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.blog.sitemaps import PostSitemap, CategorySitemap
|
||||||
from mhackspace.feeds.views import FeedViewSet, ArticleViewSet
|
from mhackspace.feeds.views import FeedViewSet, ArticleViewSet
|
||||||
from mhackspace.requests.views import RequestsForm, RequestsList
|
from mhackspace.requests.views import RequestsForm, RequestsList
|
||||||
|
from mhackspace.rfid.views import DeviceViewSet, AuthUserWithDeviceViewSet
|
||||||
|
|
||||||
from mhackspace.register.views import RegisterForm
|
from mhackspace.register.views import RegisterForm
|
||||||
|
|
||||||
from wiki.urls import get_pattern as get_wiki_pattern
|
from wiki.urls import get_pattern as get_wiki_pattern
|
||||||
from django_nyt.urls import get_pattern as get_nyt_pattern
|
from django_nyt.urls import get_pattern as get_nyt_pattern
|
||||||
|
from rest_framework_jwt.views import obtain_jwt_token
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'posts', PostViewSet)
|
router.register(r'posts', PostViewSet, 'posts')
|
||||||
router.register(r'categories', CategoryViewSet)
|
router.register(r'categories', CategoryViewSet, base_name='categories')
|
||||||
router.register(r'feeds', FeedViewSet)
|
router.register(r'feeds', FeedViewSet, 'feeds')
|
||||||
router.register(r'articles', ArticleViewSet)
|
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 = {
|
sitemaps = {
|
||||||
'posts': PostSitemap,
|
'posts': PostSitemap,
|
||||||
'category': CategorySitemap,
|
'category': CategorySitemap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'),
|
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'),
|
url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'),
|
||||||
|
@ -69,6 +78,9 @@ urlpatterns = [
|
||||||
# Django Admin, use {% url 'admin:index' %}
|
# Django Admin, use {% url 'admin:index' %}
|
||||||
url(r'{}'.format(settings.ADMIN_URL), admin.site.urls),
|
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
|
# User management
|
||||||
url(r'^users/', include('mhackspace.users.urls', namespace='users')),
|
url(r'^users/', include('mhackspace.users.urls', namespace='users')),
|
||||||
url(r'^accounts/', include('allauth.urls')),
|
url(r'^accounts/', include('allauth.urls')),
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
|
import uuid
|
||||||
import random
|
import random
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
from autofixture import AutoFixture
|
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.base import BaseCommand
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from mhackspace.base.models import BannerImage
|
from mhackspace.base.models import BannerImage
|
||||||
from mhackspace.feeds.models import Article, Feed
|
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.blog.models import Category, Post
|
||||||
|
from mhackspace.rfid.models import Device, DeviceAuth
|
||||||
|
|
||||||
|
|
||||||
class ImageFixture(AutoFixture):
|
class ImageFixture(AutoFixture):
|
||||||
|
@ -14,12 +22,23 @@ class ImageFixture(AutoFixture):
|
||||||
scaled_image = ImageGenerator(width=800, height=300, sizes=((1280, 300),))
|
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):
|
class Command(BaseCommand):
|
||||||
help = 'Build test data for development environment'
|
help = 'Build test data for development environment'
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
feeds = AutoFixture(Article)
|
feeds = AutoFixture(Article, generate_fk=True)
|
||||||
feeds.create(10)
|
feeds.create(10)
|
||||||
|
|
||||||
feed = AutoFixture(Feed)
|
feed = AutoFixture(Feed)
|
||||||
feed.create(10)
|
feed.create(10)
|
||||||
|
|
||||||
|
@ -32,14 +51,61 @@ class Command(BaseCommand):
|
||||||
# load known data
|
# load known data
|
||||||
call_command('loaddata', 'mhackspace/users/fixtures/groups.json', verbose=0)
|
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={
|
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 = ImageFixture(BannerImage)
|
||||||
banners.create(10)
|
banners.create(10)
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
'Finished creating test data'))
|
'Finished creating test data'))
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ from mhackspace.feeds.helper import import_feeds
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_homepage_feeds():
|
def update_homepage_feeds():
|
||||||
return import_feeds()
|
import_feeds()
|
||||||
|
return {'result': 'Homepage feeds imported'}
|
||||||
|
|
||||||
matrix_url = "https://matrix.org/_matrix/client/r0"
|
matrix_url = "https://matrix.org/_matrix/client/r0"
|
||||||
matrix_login_url = matrix_url + "/login"
|
matrix_login_url = matrix_url + "/login"
|
||||||
|
@ -31,9 +31,10 @@ def send_email(email_to,
|
||||||
to=[email_to],
|
to=[email_to],
|
||||||
headers={'Reply-To': 'no-reply@maidstone-hackspace.org.uk'})
|
headers={'Reply-To': 'no-reply@maidstone-hackspace.org.uk'})
|
||||||
email.send()
|
email.send()
|
||||||
|
return {'result', 'Email sent to %s' % email_to}
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def matrix_message(message):
|
def matrix_message(message, prefix=''):
|
||||||
# we dont rely on theses, so ignore if it goes wrong
|
# we dont rely on theses, so ignore if it goes wrong
|
||||||
# TODO at least log that something has gone wrong
|
# TODO at least log that something has gone wrong
|
||||||
try:
|
try:
|
||||||
|
@ -59,8 +60,8 @@ def matrix_message(message):
|
||||||
url = matrix_send_msg_url.format(**url_params)
|
url = matrix_send_msg_url.format(**url_params)
|
||||||
details = {
|
details = {
|
||||||
"msgtype": "m.text",
|
"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)
|
r2 = requests.post(url, json=details)
|
||||||
except:
|
except:
|
||||||
pass
|
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)
|
render_variations(result[0], image_variations, replace=True)
|
||||||
article.save()
|
article.save()
|
||||||
except:
|
except:
|
||||||
|
logger.exception(result)
|
||||||
|
logger.exception(result[0])
|
||||||
logger.exception('Unable to download remote image for %s' % article.original_image)
|
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 ?')
|
title = models.CharField(max_length=255, help_text='Whats being requested ?')
|
||||||
request_type = models.IntegerField(choices=REQUEST_TYPES, null=False)
|
request_type = models.IntegerField(choices=REQUEST_TYPES, null=False)
|
||||||
cost = models.DecimalField(
|
cost = models.DecimalField(
|
||||||
max_digits=4,
|
max_digits=6,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
help_text='Leave blank, if no associated cost, or add estimated cost if not sure.'
|
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):
|
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)
|
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/feeds";
|
||||||
|
|
||||||
@import "components/blog";
|
@import "components/blog";
|
||||||
|
@import "components/wiki";
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
//Django Toolbar//
|
//Django Toolbar//
|
||||||
|
@ -21,3 +22,9 @@
|
||||||
[hidden][style="display: block;"] {
|
[hidden][style="display: block;"] {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.imgfit {
|
||||||
|
width:100%;
|
||||||
|
height: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals, absolute_import
|
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.contrib.auth.models import Group
|
||||||
|
from django.utils.dateparse import parse_datetime
|
||||||
from mhackspace.users.models import Membership
|
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):
|
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:
|
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:
|
except Membership.DoesNotExist:
|
||||||
member = Membership()
|
member = Membership()
|
||||||
member.user = user
|
member.user = user
|
||||||
|
|
||||||
if complete is True:
|
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.email = signup_details.get('email')
|
||||||
member.reference = signup_details.get('reference')
|
member.reference = signup_details.get('reference')
|
||||||
member.payment = signup_details.get('amount')
|
member.payment = signup_details.get('amount')
|
||||||
member.date = signup_details.get('start_date')
|
member.date = start_date
|
||||||
|
|
||||||
member.save()
|
member.save()
|
||||||
|
|
||||||
|
@ -25,6 +37,21 @@ def create_or_update_membership(user, signup_details, complete=False):
|
||||||
return False # sign up not completed
|
return False # sign up not completed
|
||||||
|
|
||||||
# add user to group on success
|
# add user to group on success
|
||||||
|
|
||||||
|
if user:
|
||||||
|
try:
|
||||||
group = Group.objects.get(name='members')
|
group = Group.objects.get(name='members')
|
||||||
user.groups.add(group)
|
user.groups.add(group)
|
||||||
|
except:
|
||||||
|
logger.error('Members group does not exist')
|
||||||
return True # Sign up finished
|
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.contrib.auth.models import Group
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from django.core.management.base import BaseCommand
|
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
|
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):
|
def update_subscriptions(provider_name):
|
||||||
provider = select_provider('gocardless')
|
provider = select_provider('gocardless')
|
||||||
|
|
||||||
Membership.objects.all().delete()
|
Membership.objects.all().delete()
|
||||||
subscriptions = []
|
|
||||||
|
|
||||||
group = Group.objects.get(name='members')
|
group = Group.objects.get(name='members')
|
||||||
|
|
||||||
|
@ -24,18 +25,10 @@ def update_subscriptions(provider_name):
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user_model = None
|
user_model = None
|
||||||
|
|
||||||
subscriptions.append(
|
create_or_update_membership(
|
||||||
Membership(
|
|
||||||
user=user_model,
|
user=user_model,
|
||||||
email=sub.get('email'),
|
signup_details=sub,
|
||||||
reference=sub.get('reference'),
|
complete=True)
|
||||||
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])
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -49,11 +42,11 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
provider = select_provider('gocardless')
|
provider = select_provider('gocardless')
|
||||||
Membership.objects.all().delete()
|
Membership.objects.all().delete()
|
||||||
subscriptions = []
|
|
||||||
|
|
||||||
group = Group.objects.get(name='members')
|
group = Group.objects.get(name='members')
|
||||||
|
|
||||||
for sub in provider.fetch_subscriptions():
|
for sub in provider.fetch_subscriptions():
|
||||||
|
prefix = ''
|
||||||
sub['amount'] = sub['amount'] * 0.01
|
sub['amount'] = sub['amount'] * 0.01
|
||||||
try:
|
try:
|
||||||
user_model = User.objects.get(email=sub.get('email'))
|
user_model = User.objects.get(email=sub.get('email'))
|
||||||
|
@ -61,37 +54,23 @@ class Command(BaseCommand):
|
||||||
user_model.groups.add(group)
|
user_model.groups.add(group)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
user_model = None
|
user_model = None
|
||||||
self.style.NOTICE(
|
prefix = 'NO USER - '
|
||||||
'\tMissing User {reference} - {payment} - {status} - {email}'.format(**{
|
|
||||||
'reference': sub.get('reference'),
|
|
||||||
'payment': sub.get('amount'),
|
|
||||||
'status': sub.get('status'),
|
|
||||||
'email': sub.get('email')
|
|
||||||
}))
|
|
||||||
continue
|
|
||||||
|
|
||||||
create_or_update_membership(user=user_model,
|
create_or_update_membership(user=user_model,
|
||||||
signup_details=sub,
|
signup_details=sub,
|
||||||
complete=True)
|
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(
|
message = '\t{prefix}{date} - {reference} - {payment} - {status} - {email}'.format(**{
|
||||||
self.style.SUCCESS(
|
'prefix': prefix,
|
||||||
'\t{reference} - {payment} - {status} - {email}'.format(**{
|
'date': sub.get('start_date'),
|
||||||
'reference': sub.get('reference'),
|
'reference': sub.get('reference'),
|
||||||
'payment': sub.get('amount'),
|
'payment': sub.get('amount'),
|
||||||
'status': sub.get('status'),
|
'status': sub.get('status'),
|
||||||
'email': sub.get('email')
|
'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
|
from pprint import pprint
|
||||||
import pytz
|
import pytz
|
||||||
import gocardless_pro as gocardless
|
import gocardless_pro
|
||||||
import braintree
|
import braintree
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
|
||||||
PROVIDER_ID = {'gocardless':1, 'braintree': 2}
|
PROVIDER_ID = {'gocardless':1, 'braintree': 2}
|
||||||
PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'}
|
PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'}
|
||||||
|
|
||||||
|
|
||||||
def select_provider(type):
|
def select_provider(type):
|
||||||
if type == "gocardless": return gocardless_provider()
|
if type == "gocardless": return gocardless_provider()
|
||||||
if type == "braintree": return braintree_provider()
|
if type == "braintree": return braintree_provider()
|
||||||
|
@ -20,6 +21,7 @@ def select_provider(type):
|
||||||
log.exception('[scaffold] - "No Provider for ' + type)
|
log.exception('[scaffold] - "No Provider for ' + type)
|
||||||
assert 0, "No Provider for " + type
|
assert 0, "No Provider for " + type
|
||||||
|
|
||||||
|
|
||||||
class gocardless_provider:
|
class gocardless_provider:
|
||||||
"""
|
"""
|
||||||
gocardless test account details 20-00-00, 55779911
|
gocardless test account details 20-00-00, 55779911
|
||||||
|
@ -29,20 +31,10 @@ class gocardless_provider:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# gocardless are changing there api, not sure if we can switch yet
|
# 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'],
|
access_token=payment_providers['gocardless']['credentials']['access_token'],
|
||||||
environment=payment_providers['gocardless']['environment'])
|
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):
|
def fetch_customers(self):
|
||||||
"""Fetch list of customers payments"""
|
"""Fetch list of customers payments"""
|
||||||
for customer in self.client.customers.list().records:
|
for customer in self.client.customers.list().records:
|
||||||
|
@ -78,11 +70,18 @@ class gocardless_provider:
|
||||||
def get_token(self):
|
def get_token(self):
|
||||||
return 'N/A'
|
return 'N/A'
|
||||||
|
|
||||||
def cancel_subscription(self, reference):
|
def cancel_subscription(self, user, reference):
|
||||||
try:
|
try:
|
||||||
subscription = gocardless.client.subscription(reference)
|
subscription = self.client.subscriptions.get(reference)
|
||||||
response = subscription.cancel()
|
response = self.client.subscriptions.cancel(reference)
|
||||||
except gocardless.exceptions.ClientError:
|
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 {
|
return {
|
||||||
'success': False
|
'success': False
|
||||||
}
|
}
|
||||||
|
@ -90,7 +89,7 @@ class gocardless_provider:
|
||||||
'amount': subscription.amount,
|
'amount': subscription.amount,
|
||||||
'start_date': subscription.created_at,
|
'start_date': subscription.created_at,
|
||||||
'reference': subscription.id,
|
'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,
|
def create_subscription(self, user, session, amount,
|
||||||
|
@ -115,18 +114,13 @@ class gocardless_provider:
|
||||||
# response = self.client.redirect_flows.complete(r, params={
|
# response = self.client.redirect_flows.complete(r, params={
|
||||||
# "session_token": session
|
# "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 = 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
|
user_id = response.links.customer
|
||||||
mandate_id = response.links.mandate
|
mandate_id = response.links.mandate
|
||||||
# user = subscription.user()
|
|
||||||
user = self.client.customers.get(response.links.customer)
|
user = self.client.customers.get(response.links.customer)
|
||||||
mandate = self.client.mandates.get(response.links.mandate)
|
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
|
# 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
|
# 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):
|
def confirm_subscription(self, args):
|
||||||
if self.provider == 'gocardless':
|
if self.provider == 'gocardless':
|
||||||
response = gocardless.client.confirm_resource(args)
|
response = gocardless_pro.client.confirm_resource(args)
|
||||||
subscription = gocardless.client.subscription(args.get('resource_id'))
|
subscription = gocardless_pro.client.subscription(args.get('resource_id'))
|
||||||
return {
|
return {
|
||||||
'amount': subscription.amount,
|
'amount': subscription.amount,
|
||||||
'start_date': subscription.created_at,
|
'start_date': subscription.created_at,
|
||||||
|
@ -199,255 +193,3 @@ class braintree_provider:
|
||||||
'reference': paying_member.reference,
|
'reference': paying_member.reference,
|
||||||
'amount': paying_member.amount}
|
'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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from test_plus.test import TestCase
|
from test_plus.test import TestCase
|
||||||
from unittest import skip
|
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):
|
def setUp(self):
|
||||||
self.auth_gocardless()
|
super().setUp()
|
||||||
|
|
||||||
@patch('mhackspace.subscriptions.payments.gocardless.request.requests.get', autospec=True)
|
def test_unsubscribe(self):
|
||||||
def auth_gocardless(self, mock_request):
|
self.mock_success_responses()
|
||||||
# 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"
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch('gocardless.resources.Merchant') as mock_subscription:
|
result = self.provider.cancel_subscription(user=self.user, reference='M01')
|
||||||
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')
|
|
||||||
|
|
||||||
self.assertEqual(result.get('amount'), 20.00)
|
self.assertEqual(result.get('amount'), 20.00)
|
||||||
self.assertEqual(result.get('start_date'), 'date')
|
self.assertEqual(result.get('reference'), '02')
|
||||||
self.assertEqual(result.get('reference'), '01')
|
self.assertEqual(result.get('success'), True)
|
||||||
self.assertEqual(result.get('success'), 'success')
|
|
||||||
|
|
||||||
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
|
def test_confirm_subscription_callback(self):
|
||||||
@patch('mhackspace.subscriptions.payments.gocardless.client.confirm_resource', autospec=True)
|
self.mock_success_responses()
|
||||||
def test_confirm_subscription_callback(self, mock_confirm, mock_subscription):
|
membership = self.create_membership_record()
|
||||||
mock_confirm.return_value = Mock(success='success')
|
|
||||||
mock_subscription.return_value = Mock(
|
|
||||||
id='01',
|
|
||||||
status='active',
|
|
||||||
amount=20.00,
|
|
||||||
created_at='date'
|
|
||||||
)
|
|
||||||
|
|
||||||
request_params = {
|
request_params = {
|
||||||
'resource_uri': 'http://gocardless/resource/url/01',
|
'resource_uri': 'http://gocardless/resource/url/01',
|
||||||
|
@ -62,66 +36,23 @@ class TestPaymentGatewaysGocardless(TestCase):
|
||||||
'state': 'inactive'
|
'state': 'inactive'
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.provider.subscribe_confirm(request_params)
|
|
||||||
|
|
||||||
self.assertEqual(result.get('amount'), 20.00)
|
# membership = Membership.objects.get(user=self.user)
|
||||||
self.assertEqual(result.get('start_date'), 'date')
|
result = self.provider.confirm_subscription(
|
||||||
self.assertEqual(result.get('reference'), '01')
|
membership=membership,
|
||||||
self.assertEqual(result.get('success'), 'success')
|
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):
|
def test_fetch_subscription_gocardless(self):
|
||||||
item = Mock(
|
self.mock_success_responses()
|
||||||
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
|
|
||||||
for item in self.provider.fetch_subscriptions():
|
for item in self.provider.fetch_subscriptions():
|
||||||
self.assertEqual(item.get('status'), 'active')
|
self.assertEqual(item.get('status'), 'active')
|
||||||
self.assertEqual(item.get('email'), 'test@test.com')
|
self.assertEqual(item.get('email'), 'test@test.com')
|
||||||
self.assertEqual(item.get('reference'), '01')
|
self.assertEqual(item.get('reference'), '02')
|
||||||
self.assertEqual(item.get('start_date'), 'date')
|
|
||||||
self.assertEqual(item.get('amount'), 20.00)
|
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.messages.storage.fallback import FallbackStorage
|
||||||
# from django.contrib.auth.models import Group
|
# from django.contrib.auth.models import Group
|
||||||
|
from django.test import Client
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from test_plus.test import TestCase
|
from test_plus.test import TestCase
|
||||||
from mock import patch, Mock
|
from mock import patch, Mock
|
||||||
from mhackspace.users.models import Membership
|
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 (
|
from ..views import (
|
||||||
MembershipCancelView,
|
MembershipCancelView,
|
||||||
|
@ -15,53 +19,72 @@ from ..views import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseUserTestCase(TestCase):
|
class BaseUserTestCase(gocardlessMocks):
|
||||||
fixtures = ['groups']
|
fixtures = ['groups']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = self.make_user()
|
super().setUp()
|
||||||
|
# self.user = self.make_user()
|
||||||
|
# self.user.save()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
self.client = Client()
|
||||||
|
self.client.login(
|
||||||
|
username=self.user.username,
|
||||||
|
password=self.user.password)
|
||||||
|
|
||||||
|
|
||||||
class TestSubscriptionSuccessRedirectView(BaseUserTestCase):
|
class TestSubscriptionSuccessRedirectView(BaseUserTestCase):
|
||||||
@patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
|
# @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
|
||||||
@patch('mhackspace.subscriptions.views.select_provider', autospec=True)
|
# @patch('mhackspace.subscriptions.views.select_provider', autospec=True)
|
||||||
def test_success_redirect_url(self, mock_subscription, mock_provider):
|
def test_success_redirect_url(self):
|
||||||
mock_subscription.return_value = mock_provider
|
self.mock_success_responses()
|
||||||
mock_provider.confirm_subscription.return_value = {
|
self.create_membership_record()
|
||||||
'amount': 20.00,
|
# mock_gocardless.subscriptions.create.return_value = 'temp'
|
||||||
'start_date': '2017-01-01T17:07:09Z',
|
# mock_provider.confirm_subscription.return_value = {
|
||||||
'reference': 'MH0001',
|
# 'amount': 20.00,
|
||||||
'email': 'user@test.com',
|
# 'start_date': '2017-01-01T17:07:09Z',
|
||||||
'success': True
|
# 'reference': 'MH0001',
|
||||||
}
|
# 'email': 'user@test.com',
|
||||||
|
# 'success': True
|
||||||
|
# }
|
||||||
|
|
||||||
request = self.factory.post(
|
response = self.client.post(
|
||||||
reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), {
|
reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), {
|
||||||
'resource_id': 'R01',
|
'resource_id': 'R01',
|
||||||
'resource_type': 'subscription',
|
'resource_type': 'subscription',
|
||||||
'resource_url': 'https://sandbox.gocardless.com',
|
'resource_url': 'https://sandbox.gocardless.com',
|
||||||
'signature': 'test_signature'
|
'signature': 'test_signature'
|
||||||
}
|
},
|
||||||
|
follow=True
|
||||||
)
|
)
|
||||||
|
|
||||||
setattr(request, 'session', 'session')
|
# print('=============================')
|
||||||
messages = FallbackStorage(request)
|
# setattr(request, 'session', 'session')
|
||||||
setattr(request, '_messages', messages)
|
# messages = FallbackStorage(request)
|
||||||
request.user = self.user
|
# setattr(request, '_messages', messages)
|
||||||
|
# request.user = user1
|
||||||
|
|
||||||
view = MembershipJoinSuccessView()
|
# view = MembershipJoinSuccessView()
|
||||||
view.request = request
|
# view.request = request
|
||||||
self.assertEqual(
|
# print(self.user)
|
||||||
view.get_redirect_url(provider ='gocardless'),
|
self.assertRedirects(
|
||||||
reverse('users:detail', kwargs={'username': self.user.username})
|
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()
|
members = Membership.objects.all()
|
||||||
self.assertEqual(members.count(), 1)
|
self.assertEqual(members.count(), 1)
|
||||||
|
|
||||||
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
|
# @patch('mhackspace.subscriptions.payments.gocardless_pro.client.subscriptions', autospec=True)
|
||||||
def test_failure_redirect_url(self, mock_obj):
|
def test_failure_redirect_url(self):
|
||||||
|
self.mock_success_responses()
|
||||||
# Instantiate the view directly. Never do this outside a test!
|
# Instantiate the view directly. Never do this outside a test!
|
||||||
# Generate a fake request
|
# Generate a fake request
|
||||||
request = self.factory.post(
|
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.models import MEMBERSHIP_CANCELLED
|
||||||
from mhackspace.users.forms import MembershipJoinForm
|
from mhackspace.users.forms import MembershipJoinForm
|
||||||
from mhackspace.subscriptions.payments import select_provider
|
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):
|
class MembershipCancelView(LoginRequiredMixin, RedirectView):
|
||||||
|
@ -28,17 +28,12 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView):
|
||||||
member = Membership.objects.filter(user=self.request.user).first()
|
member = Membership.objects.filter(user=self.request.user).first()
|
||||||
|
|
||||||
result = provider.cancel_subscription(
|
result = provider.cancel_subscription(
|
||||||
|
user=self.request.user,
|
||||||
reference=member.reference
|
reference=member.reference
|
||||||
)
|
)
|
||||||
if result.get('success') is True:
|
|
||||||
# set membership to cancelled on success
|
|
||||||
member.status = MEMBERSHIP_CANCELLED
|
|
||||||
member.save()
|
|
||||||
|
|
||||||
|
# if result.get('success') is True:
|
||||||
# remove user from group on success
|
cancel_membership(user=self.request.user)
|
||||||
group = Group.objects.get(name='members')
|
|
||||||
self.request.user.groups.remove(group)
|
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
self.request,
|
self.request,
|
||||||
messages.SUCCESS,
|
messages.SUCCESS,
|
||||||
|
@ -68,7 +63,7 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView):
|
||||||
result = {
|
result = {
|
||||||
'email': self.request.user.email,
|
'email': self.request.user.email,
|
||||||
'reference': user_code,
|
'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()
|
'start_date': timezone.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,9 +94,10 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
payment_provider = 'gocardless'
|
payment_provider = 'gocardless'
|
||||||
provider = select_provider(payment_provider)
|
provider = select_provider(payment_provider)
|
||||||
|
print(self.request.user)
|
||||||
membership = Membership.objects.get(user=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(
|
result = provider.confirm_subscription(
|
||||||
membership=membership,
|
membership=membership,
|
||||||
session=self.request.session.session_key,
|
session=self.request.session.session_key,
|
||||||
|
@ -110,6 +106,8 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
|
||||||
)
|
)
|
||||||
|
|
||||||
# if something went wrong return to profile with an error
|
# if something went wrong return to profile with an error
|
||||||
|
|
||||||
|
result['start_date'] = timezone.now()
|
||||||
if result.get('success') is False:
|
if result.get('success') is False:
|
||||||
messages.add_message(
|
messages.add_message(
|
||||||
self.request,
|
self.request,
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% block footer-left %}{% endblock footer-left %}
|
{% block footer-left %}{% endblock footer-left %}
|
||||||
<span class="text-muted">© {% now "Y" %} Maidstone Hackspace</span>
|
<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">
|
<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-github"></i></a>
|
||||||
<a href="https://github.com/maidstone-hackspace/" target="_blank"><i class="fa fa-twitter"></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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{% if membership.get_status %}
|
{% if membership.is_active %}
|
||||||
<div id="membercard" class="registered">
|
<div id="membercard" class="registered">
|
||||||
<div class="date">Joined {{membership.date}}</div>
|
<div class="date">Joined {{membership.date}}</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -74,6 +74,7 @@
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-primary" href="{% url 'users:update' %}" role="button">My Info</a>
|
<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>
|
<a class="btn btn-primary" href="{% url 'account_email' %}" role="button">E-Mail</a>
|
||||||
</p>
|
</p>
|
||||||
<!-- Your Stuff: Custom user template urls -->
|
<!-- 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.http import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.conf.urls import url
|
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):
|
class MyUserChangeForm(UserChangeForm):
|
||||||
|
@ -46,21 +47,27 @@ class MyUserAdmin(AuthUserAdmin):
|
||||||
list_display = ('username', 'name', 'is_superuser')
|
list_display = ('username', 'name', 'is_superuser')
|
||||||
search_fields = ['name']
|
search_fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Membership)
|
||||||
|
class MembershipAdmin(ModelAdmin):
|
||||||
|
list_display = ('user_id', 'email', 'payment', 'date', 'status')
|
||||||
|
list_filter = ('status',)
|
||||||
|
|
||||||
def get_urls(self):
|
def get_urls(self):
|
||||||
urls = super(MyUserAdmin, self).get_urls()
|
urls = super(MembershipAdmin, self).get_urls()
|
||||||
my_urls = [
|
my_urls = [
|
||||||
url(r'^refresh/payments/$', self.admin_site.admin_view(self.refresh_payments))
|
url(r'^refresh/payments/$', self.admin_site.admin_view(self.refresh_payments))
|
||||||
]
|
]
|
||||||
return my_urls + urls
|
return my_urls + urls
|
||||||
|
|
||||||
def refresh_payments(self, request):
|
def refresh_payments(self, request):
|
||||||
for user in update_subscriptions(provider_name='gocardless'):
|
update_users_memebership_status()
|
||||||
continue
|
# for user in update_subscriptions(provider_name='gocardless'):
|
||||||
self.message_user(request, 'Successfully imported refresh users payment status')
|
# continue
|
||||||
return HttpResponseRedirect(reverse('admin:feeds_article_changelist'))
|
self.message_user(request, 'Successfully triggered user payment refresh')
|
||||||
|
return HttpResponseRedirect(reverse('admin:index'))
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Membership)
|
@admin.register(Rfid)
|
||||||
class MembershipAdmin(ModelAdmin):
|
class RfidAdmin(ModelAdmin):
|
||||||
list_display = ('user', 'payment', 'date', 'status')
|
list_display = ('code', 'description')
|
||||||
list_filter = ('status',)
|
|
||||||
|
|
|
@ -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)
|
skills = models.CharField(max_length=255)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
|
|
||||||
MEMBERSHIP_CANCELLED = 0
|
MEMBERSHIP_ACTIVE = 1
|
||||||
|
MEMBERSHIP_CANCELLED = 4
|
||||||
|
|
||||||
MEMBERSHIP_STATUS_CHOICES = (
|
MEMBERSHIP_STATUS_CHOICES = (
|
||||||
(0, 'Guest user'),
|
(0, 'Guest user'),
|
||||||
(1, 'Active membership'),
|
(MEMBERSHIP_ACTIVE, 'Active membership'),
|
||||||
(3, 'Membership Expired'),
|
(3, 'Membership Expired'),
|
||||||
(4, 'Membership Cancelled')
|
(MEMBERSHIP_CANCELLED, 'Membership Cancelled')
|
||||||
)
|
)
|
||||||
|
|
||||||
MEMBERSHIP_STRING = {
|
MEMBERSHIP_STRING = {
|
||||||
0: 'Guest user',
|
0: 'Guest user',
|
||||||
1: 'Active membership',
|
MEMBERSHIP_ACTIVE: 'Active membership',
|
||||||
3: 'Membership Expired'
|
3: 'Membership Expired',
|
||||||
|
MEMBERSHIP_CANCELLED: 'Membership Cancelled'
|
||||||
}
|
}
|
||||||
|
|
||||||
MEMBERSHIP_STATUS = {
|
MEMBERSHIP_STATUS = {
|
||||||
'signup': 0, # This means the user has not completed signup
|
'signup': 0, # This means the user has not completed signup
|
||||||
'active': 1,
|
'active': MEMBERSHIP_ACTIVE,
|
||||||
'cancelled': 2
|
'expired': 3,
|
||||||
|
'cancelled': MEMBERSHIP_CANCELLED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
user = models.ForeignKey(
|
user = models.OneToOneField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
default=None,
|
default=None,
|
||||||
related_name='user'
|
related_name='user',
|
||||||
|
unique=True
|
||||||
)
|
)
|
||||||
payment = models.DecimalField(max_digits=6, decimal_places=2, default=0.0)
|
payment = models.DecimalField(max_digits=6, decimal_places=2, default=0.0)
|
||||||
date = models.DateTimeField()
|
date = models.DateTimeField()
|
||||||
reference = models.CharField(max_length=255)
|
reference = models.CharField(max_length=255)
|
||||||
status = models.PositiveSmallIntegerField(default=0, choices=MEMBERSHIP_STATUS_CHOICES)
|
status = models.PositiveSmallIntegerField(default=0, choices=MEMBERSHIP_STATUS_CHOICES)
|
||||||
email = models.CharField(max_length=255)
|
email = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
return MEMBERSHIP_STRING[self.status]
|
return MEMBERSHIP_STRING[self.status]
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
if self.status is MEMBERSHIP_ACTIVE:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def lookup_status(name):
|
def lookup_status(name):
|
||||||
if not name:
|
if not name:
|
||||||
return 0
|
return 0
|
||||||
|
@ -90,3 +100,20 @@ class Membership(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.reference
|
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
|
@shared_task
|
||||||
def update_users_memebership_status():
|
def update_users_memebership_status():
|
||||||
for user in update_subscriptions(provider_name='gocardless'):
|
update_subscriptions(provider_name='gocardless')
|
||||||
continue
|
|
||||||
|
|
|
@ -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(),
|
view=views.UserUpdateView.as_view(),
|
||||||
name='update'
|
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 __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
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 django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
|
||||||
|
from .models import Rfid
|
||||||
from .models import User
|
from .models import User
|
||||||
from .models import Blurb
|
from .models import Blurb
|
||||||
from .models import Membership
|
from .models import Membership
|
||||||
|
@ -65,6 +66,18 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
return super(UserUpdateView, self).form_valid(form)
|
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):
|
class UserListView(LoginRequiredMixin, ListView):
|
||||||
model = User
|
model = User
|
||||||
# These next two lines tell the view to index lookups by username
|
# 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
|
git+git://github.com/django-wiki/django-wiki.git
|
||||||
|
|
||||||
djangorestframework==3.6.3
|
djangorestframework==3.6.3
|
||||||
|
djangorestframework-jwt
|
||||||
django-filter==1.0.2
|
django-filter==1.0.2
|
||||||
|
coreapi
|
||||||
|
# api libraries end
|
||||||
|
|
||||||
draceditor==1.1.8
|
draceditor==1.1.8
|
||||||
# django-spirit
|
# django-spirit
|
||||||
|
|
Loading…
Reference in New Issue