Merge from upstream/master

This commit is contained in:
brett 2017-10-04 17:55:34 +01:00
commit de4f11878b
52 changed files with 7170 additions and 519 deletions

View File

@ -15,7 +15,7 @@ pipeline:
- cp -n env.example .env
- mkdir -p ./cache/packages ./cache/pip
- pip install --user --cache-dir ./cache/pip -r ./requirements/test.txt
- python manage.py test mhackspace --verbosity 2
- python manage.py test mhackspace --keepdb --verbosity 2
publish-test:
pull: True

View File

@ -5,7 +5,6 @@
Repository for the maidstone hackspace website, feel free to fork this site for your own Hackspace.
** Requirements
Before getting started make sure you have git, docker and docker-compose installed on your machine.
The simplest way to setup this site is to use docker-compose so please install that from this site
@ -47,7 +46,7 @@ docker-compose -f dev.yml run --rm django python manage.py makemigrations
docker-compose -f dev.yml run --rm django python manage.py migrate
#+END_SRC
*** Create the admin user.
Once created you can login at http://127.0.0.1:8180/admin
Once created you can login at http://127.0.0.1:8180/trustee
#+BEGIN_SRC sh
docker-compose -f dev.yml run --rm django python manage.py createsuperuser
#+END_SRC
@ -74,3 +73,24 @@ docker-compose -fdev.yml run --rm django python manage.py list_subscriptions
#+BEGIN_SRC sh
docker-compose -fdev.yml run --rm django python manage.py rendervariations 'blog.Post.image' --replace
#+END_SRC
** Api
#+BEGIN_SRC python
import requests
url = 'http://127.0.0.1:8180/api/v1/rfidAuth/'
data = {
'rfid': '4996',
'device': '7bff6053-77ef-4250-ac11-8a119fd05a0e'
}
# client = RequestsClient()
response = requests.post(
'http://127.0.0.1:8180/api/v1/rfidAuth/',
data={'rfid': '238e', 'device': 'e8f27231-8093-4477-8906-e5ae1b12dbd6'})
#requests.get(url)
return response.status_code
#+END_SRC
#+RESULTS:
: 200

View File

@ -1,3 +1,3 @@
#!/bin/sh
python manage.py migrate
python manage.py runserver_plus 0.0.0.0:8000
while true; do python manage.py runserver_plus 0.0.0.0:8000; sleep 2; done

View File

@ -82,6 +82,7 @@ THIRD_PARTY_APPS = (
'whitenoise.runserver_nostatic',
'stdimage',
'rest_framework',
'django_filters',
'draceditor',
'haystack',
'djconfig',
@ -140,7 +141,7 @@ LOCAL_APPS = (
'mhackspace.blog',
'mhackspace.core',
'mhackspace.requests',
'mhackspace.register',
'mhackspace.rfid',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@ -376,7 +377,12 @@ CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='redis://redis:6379/0')
#if CELERY_BROKER_URL == 'django://':
# CELERY_RESULT_BACKEND = 'redis://'
#else:
CELERY_RESULT_BACKEND = 'django-cache'
CELERY_RESULT_BACKEND = 'redis://redis:6379/0'
CELERY_IGNORE_RESULT = False
CELERY_REDIS_HOST = "redis"
CELERY_REDIS_PORT = 6379
CELERY_REDIS_DB = 0
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
INSTALLED_APPS += ('django_celery_results','django_celery_beat',)
CELERY_TIMEZONE = 'UTC'
@ -449,10 +455,12 @@ REST_FRAMEWORK = {
'rest_framework.filters.OrderingFilter'
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
# 'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
@ -472,3 +480,4 @@ MATRIX_USER=env('MATRIX_USERNAME')
MATRIX_PASSWORD=env('MATRIX_PASSWORD')
MATRIX_ROOM=env('MATRIX_ROOM')
MSG_PREFIX = 'MH'
X_FRAME_OPTIONS = 'SAMEORIGIN'

View File

@ -86,7 +86,7 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner'
########## CELERY
# In development, all tasks will be executed locally by blocking until the task returns
CELERY_ALWAYS_EAGER = True
# CELERY_ALWAYS_EAGER = True
########## END CELERY
# Your local stuff: Below this line define 3rd party library settings
@ -99,5 +99,50 @@ CAPTCHA = {
WHITENOISE_AUTOREFRESH = True
WHITENOISE_USE_FINDERS = True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s '
'%(process)d %(thread)d %(message)s'
},
},
'handlers': {
'mail_admins': {
'level': 'DEBUG',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'logfile': {
'level':'DEBUG',
'class':'logging.FileHandler',
'filename': "%s/django.log" % ROOT_DIR,
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins', 'logfile'],
'level': 'ERROR',
'propagate': True
},
'django.security.DisallowedHost': {
'level': 'ERROR',
'handlers': ['logfile', 'console', 'mail_admins'],
'propagate': True
}
}
}
PAYMENT_PROVIDERS['gocardless']['redirect_url'] = 'http://127.0.0.1:8180'
TEMPLATE_DEBUG = False

View File

@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
#disabledd so csrf works with ajax
CSRF_COOKIE_HTTPONLY = False
X_FRAME_OPTIONS = 'DENY'
X_FRAME_OPTIONS = 'SAMEORIGIN'
# SITE CONFIGURATION
# ------------------------------------------------------------------------------

View File

@ -43,7 +43,7 @@ SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
#disabledd so csrf works with ajax
CSRF_COOKIE_HTTPONLY = False
X_FRAME_OPTIONS = 'DENY'
X_FRAME_OPTIONS = 'SAMEORIGIN'
# SITE CONFIGURATION
# ------------------------------------------------------------------------------

View File

@ -67,5 +67,8 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
]
DATABASES = {
'default': {'ENGINE': 'django.db.backends.sqlite3'}
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(str(ROOT_DIR), 'cache/test_database.db'),
}
}

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.static import static
@ -9,6 +10,7 @@ from django.views import defaults as default_views
from django.contrib.auth import views as auth_views
from django.contrib.sitemaps.views import sitemap
from rest_framework.routers import DefaultRouter
from rest_framework.documentation import include_docs_urls
from mhackspace.contact.views import contact
from mhackspace.members.views import MemberListView
@ -20,22 +22,29 @@ from mhackspace.blog.views import PostViewSet, CategoryViewSet, BlogPost, PostLi
from mhackspace.blog.sitemaps import PostSitemap, CategorySitemap
from mhackspace.feeds.views import FeedViewSet, ArticleViewSet
from mhackspace.requests.views import RequestsForm, RequestsList
from mhackspace.rfid.views import DeviceViewSet, AuthUserWithDeviceViewSet
from mhackspace.register.views import RegisterForm
from wiki.urls import get_pattern as get_wiki_pattern
from django_nyt.urls import get_pattern as get_nyt_pattern
from rest_framework_jwt.views import obtain_jwt_token
router = DefaultRouter()
router.register(r'posts', PostViewSet)
router.register(r'categories', CategoryViewSet)
router.register(r'feeds', FeedViewSet)
router.register(r'articles', ArticleViewSet)
router.register(r'posts', PostViewSet, 'posts')
router.register(r'categories', CategoryViewSet, base_name='categories')
router.register(r'feeds', FeedViewSet, 'feeds')
router.register(r'articles', ArticleViewSet, base_name='articles')
router.register(r'rfid', DeviceViewSet, base_name='rfid_device')
router.register(r'rfidAuth', AuthUserWithDeviceViewSet, base_name='device_auth')
sitemaps = {
'posts': PostSitemap,
'category': CategorySitemap,
}
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'),
url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'),
@ -69,6 +78,9 @@ urlpatterns = [
# Django Admin, use {% url 'admin:index' %}
url(r'{}'.format(settings.ADMIN_URL), admin.site.urls),
url(r'^api-token-auth/', obtain_jwt_token),
url(r'^api/docs/', include_docs_urls(title='Hackspace API')),
# User management
url(r'^users/', include('mhackspace.users.urls', namespace='users')),
url(r'^accounts/', include('allauth.urls')),

View File

@ -1,12 +1,20 @@
import uuid
import random
from django.contrib.auth.hashers import make_password
from autofixture import AutoFixture
from autofixture.generators import ImageGenerator
from autofixture.generators import (
ImageGenerator,
IntegerGenerator,
ChoicesGenerator,
Generator,
LoremWordGenerator)
from django.core.management.base import BaseCommand
from django.core.management import call_command
from mhackspace.base.models import BannerImage
from mhackspace.feeds.models import Article, Feed
from mhackspace.users.models import User
from mhackspace.users.models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
from mhackspace.blog.models import Category, Post
from mhackspace.rfid.models import Device, DeviceAuth
class ImageFixture(AutoFixture):
@ -14,12 +22,23 @@ class ImageFixture(AutoFixture):
scaled_image = ImageGenerator(width=800, height=300, sizes=((1280, 300),))
def RfidFixture():
while True:
yield str(uuid.uuid4())[0:4]
class RfidGenerator(Generator):
def generate(self):
return str(uuid.uuid4())[0:4]
class Command(BaseCommand):
help = 'Build test data for development environment'
def handle(self, *args, **options):
feeds = AutoFixture(Article)
feeds = AutoFixture(Article, generate_fk=True)
feeds.create(10)
feed = AutoFixture(Feed)
feed.create(10)
@ -32,14 +51,61 @@ class Command(BaseCommand):
# load known data
call_command('loaddata', 'mhackspace/users/fixtures/groups.json', verbose=0)
# random data
User.objects.all().delete()
Membership.objects.all().delete()
users = AutoFixture(User, field_values={
'title': random.choicee(('Mr', 'Mrs', 'Emperor', 'Captain'))
'title': ChoicesGenerator(values=('Mr', 'Mrs', 'Emperor', 'Captain')),
'password': make_password('autofixtures'),
'active': True,
'username': ChoicesGenerator(values=('Bob', 'Jane', 'Adam', 'Alice', 'Bill', 'Jill', 'Sam', 'Oly'))
}, generate_fk=True)
users.create(8)
users = AutoFixture(User, field_values={
'title': 'Mr',
'username': 'admin',
'password': make_password('autofixtures'),
'is_superuser': True,
'is_staff': True,
'is_active': True
}, generate_fk=True)
users.create(1)
user_list = User.objects.all()
members = AutoFixture(Membership, field_values={
'status': ChoicesGenerator(MEMBERSHIP_STATUS_CHOICES),
'user': ChoicesGenerator(values=user_list)
})
users.create(10)
members.create(8)
Rfid.objects.all().delete()
Device.objects.all().delete()
DeviceAuth.objects.all().delete()
rfid = AutoFixture(
Rfid,
field_values={
'code': RfidGenerator(),
'description': LoremWordGenerator()})
rfid.create(20)
device = AutoFixture(Device, field_values={
'name': ChoicesGenerator(values=('Door', 'Printer', 'Laser Cutter', ''))
})
device.create(5)
deviceauth = AutoFixture(DeviceAuth)
deviceauth.create(5)
feed = AutoFixture(Feed)
feed.create(10)
feeds = AutoFixture(Article)
feeds.create(10)
banners = ImageFixture(BannerImage)
banners.create(10)
self.stdout.write(
self.style.SUCCESS(
'Finished creating test data'))

View File

@ -6,8 +6,8 @@ from mhackspace.feeds.helper import import_feeds
@shared_task
def update_homepage_feeds():
return import_feeds()
import_feeds()
return {'result': 'Homepage feeds imported'}
matrix_url = "https://matrix.org/_matrix/client/r0"
matrix_login_url = matrix_url + "/login"
@ -31,9 +31,10 @@ def send_email(email_to,
to=[email_to],
headers={'Reply-To': 'no-reply@maidstone-hackspace.org.uk'})
email.send()
return {'result', 'Email sent to %s' % email_to}
@shared_task
def matrix_message(message):
def matrix_message(message, prefix=''):
# we dont rely on theses, so ignore if it goes wrong
# TODO at least log that something has gone wrong
try:
@ -59,8 +60,8 @@ def matrix_message(message):
url = matrix_send_msg_url.format(**url_params)
details = {
"msgtype": "m.text",
"body": "[%s] %s" % (settings.MSG_PREFIX, message)}
"body": "[%s%s] %s" % (settings.MSG_PREFIX, prefix, message)}
r2 = requests.post(url, json=details)
except:
pass
return True
return {'result', 'Matrix message sent successfully'}

27
mhackspace/base/tests.py Normal file
View File

@ -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)

View File

@ -53,6 +53,8 @@ def download_remote_images():
render_variations(result[0], image_variations, replace=True)
article.save()
except:
logger.exception(result)
logger.exception(result[0])
logger.exception('Unable to download remote image for %s' % article.original_image)

View File

@ -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),
),
]

View File

@ -21,7 +21,7 @@ class UserRequests(models.Model):
title = models.CharField(max_length=255, help_text='Whats being requested ?')
request_type = models.IntegerField(choices=REQUEST_TYPES, null=False)
cost = models.DecimalField(
max_digits=4,
max_digits=6,
decimal_places=2,
help_text='Leave blank, if no associated cost, or add estimated cost if not sure.'
)
@ -45,7 +45,7 @@ class UserRequests(models.Model):
def send_topic_update_email(sender, instance, **kwargs):
matrix_message.delay('New Request - %s' % instance.title)
matrix_message.delay(prefix=' - REQUEST', message=instance.title)
post_save.connect(send_topic_update_email, sender=UserRequests)

View File

@ -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
# )

View File

22
mhackspace/rfid/admin.py Normal file
View File

@ -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

View File

@ -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'),
),
]

View File

36
mhackspace/rfid/models.py Normal file
View File

@ -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,
)

View File

@ -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)

View File

View File

@ -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': ''
}])

41
mhackspace/rfid/views.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -9,6 +9,7 @@
@import "components/feeds";
@import "components/blog";
@import "components/wiki";
////////////////////////////////
//Django Toolbar//
@ -21,3 +22,9 @@
[hidden][style="display: block;"] {
display: block !important;
}
.imgfit {
width:100%;
height: auto;
overflow: hidden;
}

View File

@ -1,23 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
from django.contrib import messages
import logging
from datetime import datetime
from django.contrib.auth.models import Group
from django.utils.dateparse import parse_datetime
from mhackspace.users.models import Membership
from mhackspace.users.models import MEMBERSHIP_CANCELLED, MEMBERSHIP_ACTIVE
logger = logging.getLogger(__name__)
def create_or_update_membership(user, signup_details, complete=False):
start_date = signup_details.get('start_date')
if not isinstance(start_date, datetime):
start_date = parse_datetime(start_date)
try:
member = Membership.objects.get(user=user)
member = Membership.objects.get(email=signup_details.get('email'))
# Only update if newer than last record, this way we only get the latest status
# cancellation and changed payment will not be counted against current status
if start_date < member.date:
return True
except Membership.DoesNotExist:
member = Membership()
member.user = user
if complete is True:
member.status = Membership.lookup_status(name=signup_details.get('status'))
member.status = MEMBERSHIP_ACTIVE
member.email = signup_details.get('email')
member.reference = signup_details.get('reference')
member.payment = signup_details.get('amount')
member.date = signup_details.get('start_date')
member.date = start_date
member.save()
@ -25,6 +37,21 @@ def create_or_update_membership(user, signup_details, complete=False):
return False # sign up not completed
# add user to group on success
if user:
try:
group = Group.objects.get(name='members')
user.groups.add(group)
except:
logger.error('Members group does not exist')
return True # Sign up finished
def cancel_membership(user):
member = Membership.objects.get(user=user)
member.status = MEMBERSHIP_CANCELLED
member.save()
group = Group.objects.get(name='members')
user.groups.remove(group)
return True

View File

@ -1,5 +1,3 @@
from datetime import datetime
from django.utils import timezone
from django.contrib.auth.models import Group
from django.forms.models import model_to_dict
from django.core.management.base import BaseCommand
@ -8,11 +6,14 @@ from mhackspace.users.models import Membership, User
from mhackspace.subscriptions.helper import create_or_update_membership
# this should be done in bulk, create the objects and save all at once
# for now its not an issue, because of small membership size
def update_subscriptions(provider_name):
provider = select_provider('gocardless')
Membership.objects.all().delete()
subscriptions = []
group = Group.objects.get(name='members')
@ -24,18 +25,10 @@ def update_subscriptions(provider_name):
except User.DoesNotExist:
user_model = None
subscriptions.append(
Membership(
create_or_update_membership(
user=user_model,
email=sub.get('email'),
reference=sub.get('reference'),
payment=10.00,
date= sub.get('start_date'),
# date=timezone.now(),
status=Membership.lookup_status(name=sub.get('status'))
)
)
yield model_to_dict(subscriptions[-1])
signup_details=sub,
complete=True)
class Command(BaseCommand):
@ -49,11 +42,11 @@ class Command(BaseCommand):
provider = select_provider('gocardless')
Membership.objects.all().delete()
subscriptions = []
group = Group.objects.get(name='members')
for sub in provider.fetch_subscriptions():
prefix = ''
sub['amount'] = sub['amount'] * 0.01
try:
user_model = User.objects.get(email=sub.get('email'))
@ -61,37 +54,23 @@ class Command(BaseCommand):
user_model.groups.add(group)
except User.DoesNotExist:
user_model = None
self.style.NOTICE(
'\tMissing User {reference} - {payment} - {status} - {email}'.format(**{
'reference': sub.get('reference'),
'payment': sub.get('amount'),
'status': sub.get('status'),
'email': sub.get('email')
}))
continue
prefix = 'NO USER - '
create_or_update_membership(user=user_model,
signup_details=sub,
complete=True)
subscriptions.append(
Membership(
user=user_model,
email=sub.get('email'),
reference=sub.get('reference'),
payment=sub.get('amount'),
date=sub.get('start_date'),
status=Membership.lookup_status(name=sub.get('status'))
)
)
self.stdout.write(
self.style.SUCCESS(
'\t{reference} - {payment} - {status} - {email}'.format(**{
message = '\t{prefix}{date} - {reference} - {payment} - {status} - {email}'.format(**{
'prefix': prefix,
'date': sub.get('start_date'),
'reference': sub.get('reference'),
'payment': sub.get('amount'),
'status': sub.get('status'),
'email': sub.get('email')
})))
})
Membership.objects.bulk_create(subscriptions)
if user_model:
self.stdout.write(self.style.SUCCESS(message))
else:
self.stdout.write(self.style.NOTICE(message))

View File

@ -1,6 +1,6 @@
from pprint import pprint
import pytz
import gocardless_pro as gocardless
import gocardless_pro
import braintree
import logging
@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
PROVIDER_ID = {'gocardless':1, 'braintree': 2}
PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'}
def select_provider(type):
if type == "gocardless": return gocardless_provider()
if type == "braintree": return braintree_provider()
@ -20,6 +21,7 @@ def select_provider(type):
log.exception('[scaffold] - "No Provider for ' + type)
assert 0, "No Provider for " + type
class gocardless_provider:
"""
gocardless test account details 20-00-00, 55779911
@ -29,20 +31,10 @@ class gocardless_provider:
def __init__(self):
# gocardless are changing there api, not sure if we can switch yet
self.client = gocardless.Client(
self.client = gocardless_pro.Client(
access_token=payment_providers['gocardless']['credentials']['access_token'],
environment=payment_providers['gocardless']['environment'])
def subscribe_confirm(self, args):
response = gocardless.client.confirm_resource(args)
subscription = gocardless.client.subscription(args.get('resource_id'))
return {
'amount': subscription.amount,
'start_date': subscription.created_at,
'reference': subscription.id,
'success': response.success
}
def fetch_customers(self):
"""Fetch list of customers payments"""
for customer in self.client.customers.list().records:
@ -78,11 +70,18 @@ class gocardless_provider:
def get_token(self):
return 'N/A'
def cancel_subscription(self, reference):
def cancel_subscription(self, user, reference):
try:
subscription = gocardless.client.subscription(reference)
response = subscription.cancel()
except gocardless.exceptions.ClientError:
subscription = self.client.subscriptions.get(reference)
response = self.client.subscriptions.cancel(reference)
except gocardless_pro.errors.InvalidApiUsageError as e:
if e.code is 404:
logger.info('Cancel subscription failed user not found %s %s' % (e.code, e))
return {
'success': False
}
except Exception as e:
logger.info('Cancel subscription failed unknown reason code %s %s' % (e.code, e))
return {
'success': False
}
@ -90,7 +89,7 @@ class gocardless_provider:
'amount': subscription.amount,
'start_date': subscription.created_at,
'reference': subscription.id,
'success': response.get('success', False)
'success': True if response.get('status_code') is '200' else False
}
def create_subscription(self, user, session, amount,
@ -115,18 +114,13 @@ class gocardless_provider:
# response = self.client.redirect_flows.complete(r, params={
# "session_token": session
# })
response = self.client.redirect_flows.get(r)
response = self.client.redirect_flows.complete(r, params={'session_token': session})
# response = self.client.redirect_flows.get(provider_response.get('redirect_flow_id'))
# response = gocardless.client.confirm_resource(provider_response)
# subscription = gocardless.client.subscription(provider_response.get('resource_id'))
user_id = response.links.customer
mandate_id = response.links.mandate
# user = subscription.user()
user = self.client.customers.get(response.links.customer)
mandate = self.client.mandates.get(response.links.mandate)
logging.debug(user)
logging.debug(mandate)
# for some reason go cardless is in pence, so 20.00 needs to be sent as 2000
# what genious decided that was a good idea, now looks like i am charging £2000 :p
@ -180,8 +174,8 @@ class braintree_provider:
def confirm_subscription(self, args):
if self.provider == 'gocardless':
response = gocardless.client.confirm_resource(args)
subscription = gocardless.client.subscription(args.get('resource_id'))
response = gocardless_pro.client.confirm_resource(args)
subscription = gocardless_pro.client.subscription(args.get('resource_id'))
return {
'amount': subscription.amount,
'start_date': subscription.created_at,
@ -199,255 +193,3 @@ class braintree_provider:
'reference': paying_member.reference,
'amount': paying_member.amount}
class payment:
"""
https://developer.gocardless.com/api-reference/#redirect-flows-create-a-redirect-flow
paypal reference = https://github.com/paypal/PayPal-Python-SDK
gocardless reference = https://github.com/paypal/PayPal-Python-SDK
"""
#~ def __call__(self, **args):
#~ return self
def __init__(self, provider='gocardless', style='payment', mode='sandbox'):
self.provider = provider
self.environment = int(mode=='production')
self.provider_id = PROVIDER_ID.get(provider)
print(payment_providers)
if provider == 'paypal':
paypal.configure(**payment_providers[provider]['credentials'])
return
gocardless_pro.Client(
access_token=payment_providers[provider]['credentials']['access_token'],
environment=payment_providers[provider])
#~ environment = int('production' = payment_providers[provider]['environment'])
gocardless.environment = payment_providers[provider]['environment']
gocardless.set_details(**payment_providers[provider]['credentials'])
merchant = gocardless.client.merchant()
def lookup_provider_by_id(self, provider_id):
return PROVIDER_NAME.get(provider_id, None)
def make_donation(self, amount, reference, redirect_success, redirect_failure):
if self.provider == 'paypal':
payment = paypal.Payment({
"intent": "sale",
"payer": {"payment_method": "paypal"},
"redirect_urls": {
"return_url": redirect_success,
"cancel_url": redirect_failure},
"transactions": [{
"amount": {
"total": amount,
"currency": "GBP"},
"description": reference}]})
payment_response = payment.create()
print('payment create')
if payment_response:
print(payment_response)
for link in payment.links:
if link.method == "REDIRECT":
redirect_url = str(link.href)
print(redirect_url)
return str(redirect_url)
else:
print("Error while creating payment:")
print(payment.error)
if self.provider == 'gocardless':
return gocardless.client.new_bill_url(
float(amount),
name=reference,
redirect_uri=redirect_success)
return 'Error something went wrong'
def fetch_subscriptions(self):
if self.provider == 'gocardless':
merchant = gocardless.client.merchant()
for paying_member in merchant.subscriptions():
user=paying_member.user()
print(dir(paying_member))
print(paying_member.next_interval_start)
print(paying_member.status)
print(dir(paying_member.user()))
yield {
'email': user.email,
'start_date': paying_member.created_at,
'reference': paying_member.id,
'amount': paying_member.amount}
def confirm_subscription(self, args):
if self.provider == 'gocardless':
response = gocardless.client.confirm_resource(args)
subscription = gocardless.client.subscription(args.get('resource_id'))
return {
'amount': subscription.amount,
'start_date': subscription.created_at,
'reference': subscription.id
}
if self.provider == 'paypal':
print('subscribe_confirm')
payment_token = args.get('token', '')
billing_agreement_response = paypal.BillingAgreement.execute(payment_token)
amount = 0
print(billing_agreement_response)
print(billing_agreement_response.id)
for row in billing_agreement_response.plan.payment_definitions:
amount = row.amount.value
return {
'amount': amount,
'start_date': billing_agreement_response.start_date,
'reference': billing_agreement_response.id
}
return None
def unsubscribe(self, reference):
if self.provider == 'gocardless':
print('unsubscribe gocardless')
subscription = gocardless.client.subscription(reference)
print(subscription.cancel())
if self.provider == 'paypal':
# this may be wrong
# ManageRecurringPaymentsProfileStatus
print(reference)
billing_plan = paypal.BillingAgreement.find(reference)
print(billing_plan)
print(billing_plan.error)
#~ billing_plan.replace([{"op": "replace","path": "/","value": {"state":"DELETED"}}])
print(billing_plan.error)
#~ invoice = paypal.Invoice.find(reference)
options = {
"subject": "Cancelling membership",
"note": "Canceling invoice",
"send_to_merchant": True,
"send_to_payer": True
}
if billing_plan.cancel(options): # return True or False
print("Invoice[%s] cancel successfully" % (invoice.id))
else:
print(billing_plan.error)
def subscribe(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'):
if self.provider == 'gocardless':
return gocardless.client.new_subscription_url(
amount=float(amount),
interval_length=interval_length,
interval_unit=interval_unit,
name=name,
redirect_uri=redirect_success)
if self.provider == 'paypal':
billing_plan = paypal.BillingPlan({
"name": name,
"description": "Membership subscription",
"merchant_preferences": {
"auto_bill_amount": "yes",
"cancel_url": redirect_failure,
"initial_fail_amount_action": "continue",
"max_fail_attempts": "1",
"return_url": redirect_success,
"setup_fee": {
"currency": "GBP",
"value": amount
}
},
"payment_definitions": [{
"amount": {
"currency": "GBP",
"value": amount
},
"cycles": "0",
"frequency": interval_unit,
"frequency_interval": interval_length,
"name": "Regular 1",
"type": "REGULAR"
}
],
"type": "INFINITE"
})
print('create bill')
response = billing_plan.create()
billing_plan = paypal.BillingPlan.find(billing_plan.id)
if billing_plan.activate():
start_date = datetime.utcnow() + timedelta(minutes=10)
billing_agreement = paypal.BillingAgreement({
"name": billing_plan.name,
"description": name,
"start_date": start_date.strftime('%Y-%m-%dT%H:%M:%SZ'),
"plan": {"id": str(billing_plan.id)},
"payer": {"payment_method": "paypal"}
})
if billing_agreement.create():
print('billing agreement id')
print(billing_agreement.id)
for link in billing_agreement.links:
if link.rel == "approval_url":
approval_url = link.href
return approval_url
else:
print(billing_agreement.error)
print('failed')
def confirm(self, args):
confirm_details = {}
confirm_details['successfull'] = False
print('---------------------')
print(args)
from pprint import pprint
if self.provider == 'paypal':
print(args.get('paymentId'))
print(args.get('PayerID'))
payment = paypal.Payment.find(args.get('paymentId'))
pprint(payment)
print(pprint(payment))
print(payment)
confirm_details['name'] = payment['payer']['payer_info'].first_name + ' ' + payment['payer']['payer_info'].last_name
confirm_details['user'] = payment['payer']['payer_info'].email
confirm_details['status'] = payment.state
confirm_details['amount'] = payment['transactions'][0]['amount'].total
confirm_details['created'] = payment.create_time
confirm_details['reference'] = payment.id
pprint(confirm_details)
if payment.execute({"payer_id": args.get('PayerID')}): # return True or False
confirm_details['successfull'] = True
print("Payment[%s] execute successfully" % (args.get('paymentId')))
else:
print(payment.error)
return confirm_details
if self.provider == 'gocardless':
bill_id = args.get('resource_id')
gocardless.client.confirm_resource(args)
if bill_id:
bill = gocardless.client.bill(bill_id)
confirm_details['name'] = bill.name
confirm_details['user'] = bill.user
confirm_details['status'] = bill.status
confirm_details['amount'] = bill.amount
#~ confirm_details['amount_minus_fees'] = bill.amount_minus_fees
confirm_details['created'] = bill.created_at
confirm_details['reference'] = bill_id
confirm_details['successfull'] = True
return confirm_details
return None

View File

@ -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'})

View File

@ -2,57 +2,31 @@
# -*- coding: utf-8 -*-
from test_plus.test import TestCase
from unittest import skip
from mock import patch, Mock
from mock import patch, Mock, MagicMock
from mhackspace.users.models import Membership
import django.utils.timezone
from mhackspace.subscriptions.payments import payment, gocardless_provider, braintree_provider
from mhackspace.subscriptions.payments import gocardless_provider, braintree_provider
from mhackspace.subscriptions.tests.mocks import gocardlessMocks
class TestPaymentGatewaysGocardless(TestCase):
class TestPaymentGatewaysGocardless(gocardlessMocks):
def setUp(self):
self.auth_gocardless()
super().setUp()
@patch('mhackspace.subscriptions.payments.gocardless.request.requests.get', autospec=True)
def auth_gocardless(self, mock_request):
# mock braintree initalisation request
mock_request.return_value = Mock(ok=True)
mock_request.return_value.json.return_value = {
"id": "1",
"created_at": "2011-11-18T17:07:09Z",
"access_token": "test_token",
"next_payout_date": "2011-11-18T17:07:09Z"
}
def test_unsubscribe(self):
self.mock_success_responses()
with patch('gocardless.resources.Merchant') as mock_subscription:
self.provider = gocardless_provider()
return self.provider #self.provider
@skip("Need to implement")
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
def test_unsubscribe(self, mock_subscription):
mock_subscription.return_value = Mock(success='success')
mock_subscription.cancel.return_value = Mock(
id='01',
status='active',
amount=20.00,
created_at='date'
)
result = self.provider.cancel_subscription(reference='M01')
result = self.provider.cancel_subscription(user=self.user, reference='M01')
self.assertEqual(result.get('amount'), 20.00)
self.assertEqual(result.get('start_date'), 'date')
self.assertEqual(result.get('reference'), '01')
self.assertEqual(result.get('success'), 'success')
self.assertEqual(result.get('reference'), '02')
self.assertEqual(result.get('success'), True)
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
@patch('mhackspace.subscriptions.payments.gocardless.client.confirm_resource', autospec=True)
def test_confirm_subscription_callback(self, mock_confirm, mock_subscription):
mock_confirm.return_value = Mock(success='success')
mock_subscription.return_value = Mock(
id='01',
status='active',
amount=20.00,
created_at='date'
)
def test_confirm_subscription_callback(self):
self.mock_success_responses()
membership = self.create_membership_record()
request_params = {
'resource_uri': 'http://gocardless/resource/url/01',
@ -62,66 +36,23 @@ class TestPaymentGatewaysGocardless(TestCase):
'state': 'inactive'
}
result = self.provider.subscribe_confirm(request_params)
self.assertEqual(result.get('amount'), 20.00)
self.assertEqual(result.get('start_date'), 'date')
self.assertEqual(result.get('reference'), '01')
self.assertEqual(result.get('success'), 'success')
# membership = Membership.objects.get(user=self.user)
result = self.provider.confirm_subscription(
membership=membership,
session=None,
provider_response={'redirect_flow_id': 'redirect_mock_url'},
name='test')
self.assertEqual(result.get('amount'), '20.00')
self.assertEqual(result.get('reference'), '02')
self.assertEqual(result.get('success'), '200')
def test_fetch_subscription_gocardless(self):
item = Mock(
id='01',
status='active',
amount=20.00,
created_at='date'
)
item.user.return_value = Mock(email='test@test.com')
self.provider.client = Mock()
self.provider.client.subscriptions = Mock(return_value=[item])
# mock out gocardless subscriptions method, and return our own values
self.mock_success_responses()
for item in self.provider.fetch_subscriptions():
self.assertEqual(item.get('status'), 'active')
self.assertEqual(item.get('email'), 'test@test.com')
self.assertEqual(item.get('reference'), '01')
self.assertEqual(item.get('start_date'), 'date')
self.assertEqual(item.get('reference'), '02')
self.assertEqual(item.get('amount'), 20.00)
class DisabledestPaymentGatewaysBraintree(TestCase):
@patch('mhackspace.subscriptions.payments.braintree.Configuration.configure')
def auth_braintree(self, mock_request):
# mock braintree initalisation request
mock_request.return_value = Mock(ok=True)
mock_request.return_value.json.return_value = {
"id": "1",
"created_at": "2011-11-18T17:07:09Z",
"access_token": "test_token",
"next_payout_date": "2011-11-18T17:07:09Z"
}
self.provider = braintree_provider()
@patch('mhackspace.subscriptions.payments.braintree.Subscription.search')
def test_fetch_subscription_braintree(self, mock_request):
provider = self.auth_braintree()
items = [Mock(
id='01',
status='active',
amount=20.00,
reference='ref01',
created_at='date'
)]
items[-1].user.return_value = Mock(email='test@test.com')
mock_request.return_value = items
for item in self.provider.fetch_subscriptions():
self.assertEqual(item.get('status'), 'active')
self.assertEqual(item.get('email'), 'test@test.com')
self.assertEqual(item.get('reference'), 'ref01')
self.assertEqual(item.get('start_date'), 'date')
self.assertEqual(item.get('amount'), 20.00)

View File

@ -1,11 +1,15 @@
from django.contrib.messages.storage.fallback import FallbackStorage
# from django.contrib.auth.models import Group
from django.test import Client
from django.test import RequestFactory
from django.core.urlresolvers import reverse
from test_plus.test import TestCase
from mock import patch, Mock
from mhackspace.users.models import Membership
from mhackspace.users.models import Membership
from mhackspace.users.models import User
from mhackspace.subscriptions.payments import gocardless_provider
from mhackspace.subscriptions.tests.mocks import gocardlessMocks
from ..views import (
MembershipCancelView,
@ -15,53 +19,72 @@ from ..views import (
)
class BaseUserTestCase(TestCase):
class BaseUserTestCase(gocardlessMocks):
fixtures = ['groups']
def setUp(self):
self.user = self.make_user()
super().setUp()
# self.user = self.make_user()
# self.user.save()
self.factory = RequestFactory()
self.client = Client()
self.client.login(
username=self.user.username,
password=self.user.password)
class TestSubscriptionSuccessRedirectView(BaseUserTestCase):
@patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
@patch('mhackspace.subscriptions.views.select_provider', autospec=True)
def test_success_redirect_url(self, mock_subscription, mock_provider):
mock_subscription.return_value = mock_provider
mock_provider.confirm_subscription.return_value = {
'amount': 20.00,
'start_date': '2017-01-01T17:07:09Z',
'reference': 'MH0001',
'email': 'user@test.com',
'success': True
}
# @patch('mhackspace.subscriptions.payments.gocardless_provider', autospec=True)
# @patch('mhackspace.subscriptions.views.select_provider', autospec=True)
def test_success_redirect_url(self):
self.mock_success_responses()
self.create_membership_record()
# mock_gocardless.subscriptions.create.return_value = 'temp'
# mock_provider.confirm_subscription.return_value = {
# 'amount': 20.00,
# 'start_date': '2017-01-01T17:07:09Z',
# 'reference': 'MH0001',
# 'email': 'user@test.com',
# 'success': True
# }
request = self.factory.post(
response = self.client.post(
reverse('join_hackspace_success', kwargs={'provider': 'gocardless'}), {
'resource_id': 'R01',
'resource_type': 'subscription',
'resource_url': 'https://sandbox.gocardless.com',
'signature': 'test_signature'
}
},
follow=True
)
setattr(request, 'session', 'session')
messages = FallbackStorage(request)
setattr(request, '_messages', messages)
request.user = self.user
# print('=============================')
# setattr(request, 'session', 'session')
# messages = FallbackStorage(request)
# setattr(request, '_messages', messages)
# request.user = user1
view = MembershipJoinSuccessView()
view.request = request
self.assertEqual(
view.get_redirect_url(provider ='gocardless'),
reverse('users:detail', kwargs={'username': self.user.username})
)
# view = MembershipJoinSuccessView()
# view.request = request
# print(self.user)
self.assertRedirects(
response,
expected_url='/accounts/login/?next=/membership/gocardless/success',
status_code=302,
target_status_code=200)
# self.assertEqual(
# view.get_redirect_url(provider ='gocardless'),
# reverse('users:detail', kwargs={'username': self.user.username})
# )
# view = Memhttps://www.youtube.com/bershipJoinSuccessView()
# view.request = request
members = Membership.objects.all()
self.assertEqual(members.count(), 1)
@patch('mhackspace.subscriptions.payments.gocardless.client.subscription', autospec=True)
def test_failure_redirect_url(self, mock_obj):
# @patch('mhackspace.subscriptions.payments.gocardless_pro.client.subscriptions', autospec=True)
def test_failure_redirect_url(self):
self.mock_success_responses()
# Instantiate the view directly. Never do this outside a test!
# Generate a fake request
request = self.factory.post(

View File

@ -14,7 +14,7 @@ from mhackspace.users.models import User, Membership
from mhackspace.users.models import MEMBERSHIP_CANCELLED
from mhackspace.users.forms import MembershipJoinForm
from mhackspace.subscriptions.payments import select_provider
from mhackspace.subscriptions.helper import create_or_update_membership
from mhackspace.subscriptions.helper import create_or_update_membership, cancel_membership
class MembershipCancelView(LoginRequiredMixin, RedirectView):
@ -28,17 +28,12 @@ class MembershipCancelView(LoginRequiredMixin, RedirectView):
member = Membership.objects.filter(user=self.request.user).first()
result = provider.cancel_subscription(
user=self.request.user,
reference=member.reference
)
if result.get('success') is True:
# set membership to cancelled on success
member.status = MEMBERSHIP_CANCELLED
member.save()
# remove user from group on success
group = Group.objects.get(name='members')
self.request.user.groups.remove(group)
# if result.get('success') is True:
cancel_membership(user=self.request.user)
messages.add_message(
self.request,
messages.SUCCESS,
@ -68,7 +63,7 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView):
result = {
'email': self.request.user.email,
'reference': user_code,
'amount': form_subscription.cleaned_data.get('amount', 20.00) * 0.01,
'amount': form_subscription.cleaned_data.get('amount', 20.00),
'start_date': timezone.now()
}
@ -99,6 +94,7 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
payment_provider = 'gocardless'
provider = select_provider(payment_provider)
print(self.request.user)
membership = Membership.objects.get(user=self.request.user)
name = "Membership your membership id is MH%s" % membership.reference
@ -110,6 +106,8 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
)
# if something went wrong return to profile with an error
result['start_date'] = timezone.now()
if result.get('success') is False:
messages.add_message(
self.request,

View File

@ -168,6 +168,7 @@
<div class="container">
{% block footer-left %}{% endblock footer-left %}
<span class="text-muted">&copy; {% now "Y" %} Maidstone Hackspace</span>
<span><a href="https://maidstone-hackspace.org.uk/wiki/maidstone-hackspace-constitution/">Constitution</a></span>
<div class="float-right">
<a href="https://github.com/maidstone-hackspace/" target="_blank"><i class="fa fa-github"></i></a>
<a href="https://github.com/maidstone-hackspace/" target="_blank"><i class="fa fa-twitter"></i></a>

View File

@ -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 %}

View File

@ -34,7 +34,7 @@
{% endif %}
</div>
<div class="col-md-6">
{% if membership.get_status %}
{% if membership.is_active %}
<div id="membercard" class="registered">
<div class="date">Joined {{membership.date}}</div>
<div class="container">
@ -74,6 +74,7 @@
<div class="col-sm-12">
<p>
<a class="btn btn-primary" href="{% url 'users:update' %}" role="button">My Info</a>
<a class="btn btn-primary" href="{% url 'users:access_cards' %}" role="button">My Rfid Cards</a>
<a class="btn btn-primary" href="{% url 'account_email' %}" role="button">E-Mail</a>
</p>
<!-- Your Stuff: Custom user template urls -->

View File

@ -9,9 +9,10 @@ from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.conf.urls import url
from .models import User, Membership, MEMBERSHIP_STATUS_CHOICES
from .models import User, Rfid, Membership, MEMBERSHIP_STATUS_CHOICES
from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
# from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
from mhackspace.users.tasks import update_users_memebership_status
class MyUserChangeForm(UserChangeForm):
@ -46,21 +47,27 @@ class MyUserAdmin(AuthUserAdmin):
list_display = ('username', 'name', 'is_superuser')
search_fields = ['name']
@admin.register(Membership)
class MembershipAdmin(ModelAdmin):
list_display = ('user_id', 'email', 'payment', 'date', 'status')
list_filter = ('status',)
def get_urls(self):
urls = super(MyUserAdmin, self).get_urls()
urls = super(MembershipAdmin, self).get_urls()
my_urls = [
url(r'^refresh/payments/$', self.admin_site.admin_view(self.refresh_payments))
]
return my_urls + urls
def refresh_payments(self, request):
for user in update_subscriptions(provider_name='gocardless'):
continue
self.message_user(request, 'Successfully imported refresh users payment status')
return HttpResponseRedirect(reverse('admin:feeds_article_changelist'))
update_users_memebership_status()
# for user in update_subscriptions(provider_name='gocardless'):
# continue
self.message_user(request, 'Successfully triggered user payment refresh')
return HttpResponseRedirect(reverse('admin:index'))
@admin.register(Membership)
class MembershipAdmin(ModelAdmin):
list_display = ('user', 'payment', 'date', 'status')
list_filter = ('status',)
@admin.register(Rfid)
class RfidAdmin(ModelAdmin):
list_display = ('code', 'description')

View File

@ -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)),
],
),
]

View File

@ -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 = [
]

View File

@ -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 = [
]

View File

@ -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 = [
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -45,44 +45,54 @@ class Blurb(models.Model):
skills = models.CharField(max_length=255)
description = models.TextField()
MEMBERSHIP_CANCELLED = 0
MEMBERSHIP_ACTIVE = 1
MEMBERSHIP_CANCELLED = 4
MEMBERSHIP_STATUS_CHOICES = (
(0, 'Guest user'),
(1, 'Active membership'),
(MEMBERSHIP_ACTIVE, 'Active membership'),
(3, 'Membership Expired'),
(4, 'Membership Cancelled')
(MEMBERSHIP_CANCELLED, 'Membership Cancelled')
)
MEMBERSHIP_STRING = {
0: 'Guest user',
1: 'Active membership',
3: 'Membership Expired'
MEMBERSHIP_ACTIVE: 'Active membership',
3: 'Membership Expired',
MEMBERSHIP_CANCELLED: 'Membership Cancelled'
}
MEMBERSHIP_STATUS = {
'signup': 0, # This means the user has not completed signup
'active': 1,
'cancelled': 2
'active': MEMBERSHIP_ACTIVE,
'expired': 3,
'cancelled': MEMBERSHIP_CANCELLED
}
class Membership(models.Model):
user = models.ForeignKey(
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
null=True, blank=True,
default=None,
related_name='user'
related_name='user',
unique=True
)
payment = models.DecimalField(max_digits=6, decimal_places=2, default=0.0)
date = models.DateTimeField()
reference = models.CharField(max_length=255)
status = models.PositiveSmallIntegerField(default=0, choices=MEMBERSHIP_STATUS_CHOICES)
email = models.CharField(max_length=255)
email = models.CharField(max_length=255, unique=True)
@property
def get_status(self):
return MEMBERSHIP_STRING[self.status]
def is_active(self):
if self.status is MEMBERSHIP_ACTIVE:
return True
return False
def lookup_status(name):
if not name:
return 0
@ -90,3 +100,20 @@ class Membership(models.Model):
def __str__(self):
return self.reference
# users rfid card to user mapping, user can have more than one card
class Rfid(models.Model):
code = models.CharField(max_length=7)
description = models.CharField(_('Short rfid description'), blank=True, max_length=255)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
# related_name='rfid_user'
)
def __str__(self):
return self.description
def name(self):
return self.user.name

View File

@ -4,5 +4,4 @@ from mhackspace.subscriptions.management.commands.update_membership_status impor
@shared_task
def update_users_memebership_status():
for user in update_subscriptions(provider_name='gocardless'):
continue
update_subscriptions(provider_name='gocardless')

View File

@ -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),
)

View File

@ -26,4 +26,9 @@ urlpatterns = [
view=views.UserUpdateView.as_view(),
name='update'
),
url(
regex=r'^-access-cards$',
view=views.RfidCardsUpdateView.as_view(),
name='access_cards'
),
]

View File

@ -2,10 +2,11 @@
from __future__ import absolute_import, unicode_literals
from django.core.urlresolvers import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.views.generic import DetailView, ListView, RedirectView, UpdateView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Rfid
from .models import User
from .models import Blurb
from .models import Membership
@ -65,6 +66,18 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return super(UserUpdateView, self).form_valid(form)
class RfidCardsUpdateView(LoginRequiredMixin, CreateView):
fields = ['user', 'code', 'description', ]
model = Rfid
def form_valid(self, form):
user = self.request.user
form.instance.user = user
return super(RfidCardsUpdateView, self).form_valid(form)
class UserListView(LoginRequiredMixin, ListView):
model = User
# These next two lines tell the view to index lookups by username

View File

@ -61,7 +61,10 @@ git+https://github.com/olymk2/scaffold.git
git+git://github.com/django-wiki/django-wiki.git
djangorestframework==3.6.3
djangorestframework-jwt
django-filter==1.0.2
coreapi
# api libraries end
draceditor==1.1.8
# django-spirit