change subscription mechanism to use new gocardless api

This commit is contained in:
Oly 2017-08-14 14:00:59 +01:00
parent b3d580d49c
commit 54e1bce190
26 changed files with 414 additions and 128 deletions

3
compose/data/cache/README.org vendored Normal file
View File

@ -0,0 +1,3 @@
* cache folder
Store things like pip cache here so wee dont have to download every time wee build.

View File

@ -0,0 +1,3 @@
* Logs folder
Store logs generated by containers you want to keep in this folder, usefull if your container fails on startup

View File

@ -10,15 +10,52 @@ https://docs.djangoproject.com/en/dev/ref/settings/
"""
from __future__ import absolute_import, unicode_literals
import os
import time
import environ
# from spirit.settings import *
ROOT_DIR = environ.Path(__file__) - 3 # (mhackspace/config/settings/common.py - 3 = mhackspace/)
APPS_DIR = ROOT_DIR.path('mhackspace')
env = environ.Env()
env.read_env('%s/.env' % ROOT_DIR)
ST_TOPIC_PRIVATE_CATEGORY_PK = 1
ST_RATELIMIT_ENABLE = True
ST_RATELIMIT_CACHE_PREFIX = 'srl'
ST_RATELIMIT_CACHE = 'default'
ST_RATELIMIT_SKIP_TIMEOUT_CHECK = False
ST_NOTIFICATIONS_PER_PAGE = 20
ST_COMMENT_MAX_LEN = 3000
ST_MENTIONS_PER_COMMENT = 30
ST_DOUBLE_POST_THRESHOLD_MINUTES = 30
ST_YT_PAGINATOR_PAGE_RANGE = 3
ST_SEARCH_QUERY_MIN_LEN = 3
ST_USER_LAST_SEEN_THRESHOLD_MINUTES = 1
ST_PRIVATE_FORUM = False
ST_ALLOWED_UPLOAD_IMAGE_FORMAT = ('jpeg', 'png', 'gif')
ST_ALLOWED_URL_PROTOCOLS = {
'http', 'https', 'mailto', 'ftp', 'ftps',
'git', 'svn', 'magnet', 'irc', 'ircs'}
ST_UNICODE_SLUGS = True
ST_UNIQUE_EMAILS = True
ST_CASE_INSENSITIVE_EMAILS = True
# Tests helpers
ST_TESTS_RATELIMIT_NEVER_EXPIRE = False
ST_BASE_DIR = os.path.dirname(__file__)
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'search', 'whoosh_index'),
},
}
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
@ -40,11 +77,41 @@ THIRD_PARTY_APPS = (
'allauth.socialaccount', # registration
'allauth.socialaccount.providers.google', # registration
'allauth.socialaccount.providers.github', # registration
'allauth.socialaccount.providers.facebook', # registration
# 'allauth.socialaccount.providers.facebook', # registration
'whitenoise.runserver_nostatic',
'stdimage',
'rest_framework',
'draceditor',
'haystack',
'djconfig',
'spirit.core',
'spirit.admin',
'spirit.search',
'spirit.user',
'spirit.user.admin',
'spirit.user.auth',
'spirit.category',
'spirit.category.admin',
'spirit.topic',
'spirit.topic.admin',
'spirit.topic.favorite',
'spirit.topic.moderate',
'spirit.topic.notification',
'spirit.topic.poll', # todo: remove in Spirit v0.6
'spirit.topic.private',
'spirit.topic.unread',
'spirit.comment',
'spirit.comment.bookmark',
'spirit.comment.flag',
'spirit.comment.flag.admin',
'spirit.comment.history',
'spirit.comment.like',
'spirit.comment.poll',
)
# Apps specific for this project go here.
@ -74,6 +141,16 @@ MIDDLEWARE = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
#fix for ip logging behind a proxy
'x_forwarded_for.middleware.XForwardedForMiddleware',
'djconfig.middleware.DjConfigMiddleware',
'spirit.user.middleware.TimezoneMiddleware',
'spirit.user.middleware.LastIPMiddleware',
'spirit.user.middleware.LastSeenMiddleware',
'spirit.user.middleware.ActiveUserMiddleware',
'spirit.core.middleware.PrivateForumMiddleware',
)
# MIGRATIONS CONFIGURATION
@ -171,6 +248,7 @@ TEMPLATES = [
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
# Your stuff: custom template context processors go here
'djconfig.context_processors.config',
],
},
},

View File

@ -47,14 +47,14 @@ CACHES = {
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
}
},
'st_rate_limit': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'spirit_rl_cache',
'TIMEOUT': None
}
}
# CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
# 'LOCATION': ''
# }
# }
# django-debug-toolbar
# ------------------------------------------------------------------------------
@ -92,3 +92,5 @@ CAPTCHA = {
WHITENOISE_AUTOREFRESH = True
WHITENOISE_USE_FINDERS = True
PAYMENT_PROVIDERS['gocardless']['redirect_url'] = 'http://127.0.0.1:8180'

View File

@ -151,6 +151,11 @@ CACHES = {
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
}
},
'st_rate_limit': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'spirit_rl_cache',
'TIMEOUT': None
}
}

View File

@ -152,6 +152,11 @@ CACHES = {
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
}
},
'st_rate_limit': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'spirit_rl_cache',
'TIMEOUT': None
}
}

View File

@ -36,6 +36,11 @@ CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': ''
},
'st_rate_limit': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'spirit_rl_cache',
'TIMEOUT': None
}
}

View File

@ -1,6 +1,5 @@
# -*- 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
@ -21,6 +20,7 @@ from mhackspace.blog.views import PostViewSet, CategoryViewSet, BlogPost, PostLi
from mhackspace.blog.sitemaps import PostSitemap, CategorySitemap
from mhackspace.feeds.views import FeedViewSet, ArticleViewSet
# import spirit.urls
router = DefaultRouter()
router.register(r'posts', PostViewSet)
router.register(r'categories', CategoryViewSet)
@ -39,6 +39,7 @@ urlpatterns = [
url(r'^mailing-list/$', TemplateView.as_view(template_name='pages/mailing-list.html'), name='group'),
url(r'^contact/$', contact, name='contact'),
url(r'^discuss/', include('spirit.urls')),
url(r'^api/v1/', include(router.urls, namespace='v1')),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^draceditor/', include('draceditor.urls')),
@ -79,6 +80,7 @@ urlpatterns = [
url(r'^reset/done/$', auth_views.password_reset_complete, name='password_reset_complete'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.

View File

@ -20,9 +20,10 @@ services:
command: /start-dev.sh
depends_on:
- postgres
environment:
- POSTGRES_USER=mhackspace
- USE_DOCKER=yes
env_file: .env
# environment:
# - POSTGRES_USER=mhackspace
# - USE_DOCKER=yes
volumes:
- .:/app
ports:

View File

@ -28,6 +28,7 @@ services:
env_file: .env
volumes:
- .:/app
- ./compose/data/logs:/var/log/gunicorn
- sockets:/data/sockets
node:

View File

@ -1,3 +1,4 @@
import random
from autofixture import AutoFixture
from autofixture.generators import ImageGenerator
from django.core.management.base import BaseCommand
@ -7,10 +8,12 @@ from mhackspace.feeds.models import Article, Feed
from mhackspace.users.models import User
from mhackspace.blog.models import Category, Post
class ImageFixture(AutoFixture):
class Values:
scaled_image = ImageGenerator(width=800, height=300, sizes=((1280, 300),))
class Command(BaseCommand):
help = 'Build test data for development environment'
@ -30,10 +33,11 @@ class Command(BaseCommand):
call_command('loaddata', 'mhackspace/users/fixtures/groups.json', verbose=0)
# random data
users = AutoFixture(User)
users = AutoFixture(User, field_values={
'title': random.choicee(('Mr', 'Mrs', 'Emperor', 'Captain'))
})
users.create(10)
banners = ImageFixture(BannerImage)
banners.create(10)
self.stdout.write(

View File

@ -6,3 +6,11 @@
transform: rotate(360deg);
}
}
body {
max-width: 100%;
}
.navbar-brand {
width:100%;
}

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
from django.contrib import messages
from django.contrib.auth.models import Group
from mhackspace.users.models import Membership
def create_or_update_membership(user, signup_details, complete=False):
try:
member = Membership.objects.get(user=user)
except Membership.DoesNotExist:
member = Membership()
member.user = user
if complete is True:
member.status = Membership.lookup_status(name=signup_details.get('status'))
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.save()
if complete is False:
return False # sign up not completed
# add user to group on success
group = Group.objects.get(name='members')
user.groups.add(group)
return True # Sign up finished

View File

@ -22,20 +22,16 @@ class Command(BaseCommand):
payment_objects = []
for customer in provider.fetch_customers():
# self.stdout.write(str(dir(customer)))
# self.stdout.write(str(customer))
user = User.objects.get(email=customer.get('email'))
payment_objects.append(Payments(
user=None,
user_reference=customer.get('user_id'),
user=user,
user_reference=customer.get('user_reference'),
user_email=customer.get('email'),
reference=customer.get('payment_id'),
amount=customer.get('amount'),
type=Payments.lookup_payment_type(customer.get('payment_type')),
date=customer.get('payment_date')
))
# self.stdout.write(str(customer.email))
# self.stdout.write(str(dir(customer['email']())))
self.stdout.write(
self.style.SUCCESS(
'\t{reference} - {amount} - {type} - {user_email}'.format(**model_to_dict(payment_objects[-1]))))

View File

@ -5,7 +5,7 @@ from django.forms.models import model_to_dict
from django.core.management.base import BaseCommand
from mhackspace.subscriptions.payments import select_provider
from mhackspace.users.models import Membership, User
from mhackspace.subscriptions.helper import create_or_update_membership
def update_subscriptions(provider_name):
@ -54,28 +54,44 @@ class Command(BaseCommand):
group = Group.objects.get(name='members')
for sub in provider.fetch_subscriptions():
sub['amount'] = sub['amount'] * 0.01
try:
user_model = User.objects.get(email=sub.get('email'))
if sub.get('status') == 'active':
user_model.groups.add(group)
except User.DoesNotExist:
user_model = None
self.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
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=10.00,
date= sub.get('start_date'),
# date=timezone.now(),
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(**model_to_dict(subscriptions[-1]))))
'\t{reference} - {payment} - {status} - {email}'.format(**{
'reference': sub.get('reference'),
'payment': sub.get('amount'),
'status': sub.get('status'),
'email': sub.get('email')
})))
Membership.objects.bulk_create(subscriptions)

View File

@ -1,19 +1,14 @@
from pprint import pprint
import pytz
import gocardless
import gocardless_pro as gocardless
import braintree
import logging
from django.conf import settings
payment_providers = settings.PAYMENT_PROVIDERS
# import gocardless_pro
logger = logging.getLogger(__name__)
# import paypalrestsdk as paypal
# from website.config import settings
# from website.config.import app_domain
# from website.config.logger import log
PROVIDER_ID = {'gocardless':1, 'braintree': 2}
PROVIDER_NAME = {1: 'gocardless', 2: 'braintree'}
@ -26,19 +21,17 @@ def select_provider(type):
assert 0, "No Provider for " + type
class gocardless_provider:
"""
gocardless test account details 20-00-00, 55779911
"""
form_remote = True
client = None
def __init__(self):
# gocardless are changing there api, not sure if we can switch yet
# self.client = gocardless_pro.Client(
# access_token=payment_providers['gocardless']['credentials']['access_token'],
# environment=payment_providers['gocardless']['environment'])
print(payment_providers.keys)
gocardless.environment = payment_providers['gocardless']['environment']
gocardless.set_details(**payment_providers['gocardless']['credentials'])
self.client = gocardless.client.merchant()
self.client = gocardless.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)
@ -50,49 +43,33 @@ class gocardless_provider:
'success': response.success
}
def fetch_customers(self):
merchant = gocardless.client.merchant()
for customer in merchant.bills():
user = customer.user()
# print(dir(customer))
# print(dir(customer.reference_fields))
# print(customer.reference_fields)
# print(customer.payout_id)
# print(customer.reference_fields.payout_id)
result = {
'user_id': user.id,
'email': user.email,
'status': customer.status,
'payment_id': customer.id,
'payment_type': customer.source_type,
'payment_date': customer.created_at,
'amount': customer.amount
}
yield result #customer
"""Fetch list of customers payments"""
for customer in self.client.customers.list().records:
for payment in self.client.payments.list(params={"customer": customer.id}).records:
yield {
'user_reference': customer.id,
'email': customer.email,
'status': payment.status,
'payment_id': payment.links.subscription,
'payment_type': 'subscription' if payment.links.subscription else 'payment',
'payment_date': payment.created_at,
'amount': payment.amount
}
# for customer in self.client.users():
# result = {
# 'email': customer.email,
# 'created_date': customer.created_at,
# 'first_name': customer.first_name,
# 'last_name': customer.last_name
# }
# yield customer
def fetch_subscriptions(self):
for paying_member in self.client.subscriptions():
user=paying_member.user()
# for paying_member in self.client.mandates.list().records:
for paying_member in self.client.subscriptions.list().records:
mandate=self.client.mandates.get(paying_member.links.mandate)
user=self.client.customers.get(mandate.links.customer)
# gocardless does not have a reference so we use the id instead
yield {
'status': paying_member.status,
'email': user.email,
'start_date': paying_member.created_at,
'reference': paying_member.id,
'reference': mandate.id,
'amount': paying_member.amount}
def get_redirect_url(self):
@ -116,26 +93,62 @@ class gocardless_provider:
'success': response.get('success', False)
}
def create_subscription(self, amount, name, redirect_success, redirect_failure, interval_unit='month', interval_length='1'):
return gocardless.client.new_subscription_url(
amount=float(amount),
interval_length=interval_length,
interval_unit=interval_unit,
name=name,
redirect_uri=redirect_success)
def create_subscription(self, user, session, amount,
name, redirect_success, redirect_failure,
interval_unit='monthly', interval_length='1'):
return self.client.redirect_flows.create(params={
"description": name,
"session_token": session,
"success_redirect_url": redirect_success,
"prefilled_customer": {
"given_name": user.first_name,
"family_name": user.last_name,
"email": user.email
}
})
def confirm_subscription(self, provider_response):
response = gocardless.client.confirm_resource(provider_response)
subscription = gocardless.client.subscription(provider_response.get('resource_id'))
user = subscription.user()
def confirm_subscription(self, membership, session, provider_response,
name, interval_unit='monthly', interval_length='1'):
r = provider_response.get('redirect_flow_id')
# response = self.client.redirect_flows.complete(r, params={
# "session_token": session
# })
response = self.client.redirect_flows.get(r)
# 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
# the return is the same so you need to convert on send and receive
subscription_response = self.client.subscriptions.create(
params={
'amount': str(membership.payment).replace('.', ''),
'currency': 'GBP',
'interval_unit': interval_unit,
'name': name,
# 'metadata': {'reference': },
'links': {'mandate': mandate_id}
})
return {
'amount': subscription.amount,
'amount': membership.payment,
'email': user.email,
'start_date': subscription.created_at,
'reference': subscription.id,
'success': response.get('success')
'start_date': subscription_response.created_at,
'reference': mandate_id,
'success': subscription_response.api_response.status_code
}
class braintree_provider:
form_remote = False
@ -189,6 +202,7 @@ class braintree_provider:
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
"""

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
from django.utils import timezone
from django.conf import settings
from django.shortcuts import redirect
from django.views.generic import UpdateView, RedirectView
@ -13,6 +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
class MembershipCancelView(LoginRequiredMixin, RedirectView):
@ -54,7 +56,6 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView):
return User.objects.get(username=self.request.user.username)
def form_valid(self, form):
app_domain = 'http://test.maidstone-hackspace.org.uk'
payment_provider = 'gocardless'
provider = select_provider(payment_provider)
app_domain = provider.get_redirect_url()
@ -64,16 +65,31 @@ class MembershipJoinView(LoginRequiredMixin, UpdateView):
form_subscription = MembershipJoinForm(data=self.request.POST)
form_subscription.is_valid()
result = {
'email': self.request.user.email,
'reference': user_code,
'amount': form_subscription.cleaned_data.get('amount', 20.00) * 0.01,
'start_date': timezone.now()
}
create_or_update_membership(
user=self.request.user,
signup_details=result,
complete=False
)
success_url = '%s/membership/%s/success' % (app_domain, payment_provider)
failure_url = '%s/membership/%s/failure' % (app_domain, payment_provider)
url = provider.create_subscription(
user=self.request.user,
session=self.request.session.session_key,
amount=form_subscription.cleaned_data.get('amount', 20.00),
name="Membership your membership id is MH%s" % user_code,
redirect_success=success_url,
redirect_failure=failure_url
)
return redirect(url)
return redirect(url.redirect_url)
class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
@ -83,11 +99,17 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
payment_provider = 'gocardless'
provider = select_provider(payment_provider)
membership = Membership.objects.get(user=self.request.user)
name="Membership your membership id is MH%s" % membership.reference
result = provider.confirm_subscription(
provider_response=self.request.GET
membership=membership,
session=self.request.session.session_key,
provider_response=self.request.GET,
name=name
)
#if something went wrong return to profile with an error
# if something went wrong return to profile with an error
if result.get('success') is False:
messages.add_message(
self.request,
@ -96,27 +118,15 @@ class MembershipJoinSuccessView(LoginRequiredMixin, RedirectView):
return super(MembershipJoinSuccessView, self).get_redirect_url(*args, **kwargs)
del(kwargs['provider'])
try:
member = Membership.objects.get(user=self.request.user)
except Membership.DoesNotExist:
member = Membership()
member.user = self.request.user
member.email = result.get('email')
member.reference = result.get('reference')
member.payment = result.get('amount')
member.date = result.get('start_date')
member.status = Membership.lookup_status(name=result.get('status'))
member.save()
if create_or_update_membership(user=self.request.user,
signup_details=result,
complete=True) is True:
messages.add_message(
self.request,
messages.SUCCESS,
'Success your membership should now be active')
kwargs['username'] = self.request.user.get_username()
# add user to group on success
group = Group.objects.get(name='members')
self.request.user.groups.add(group)
messages.add_message(
self.request,
messages.SUCCESS,
'Success your membership should now be active')
return super(MembershipJoinSuccessView, self).get_redirect_url(*args, **kwargs)

View File

@ -27,11 +27,12 @@
<meta name="msapplication-config" content="{% static 'browserconfig.xml' %}">
<meta name="theme-color" content="#008080">
<link href="{% sass_src 'sass/project.scss' %}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
{% block css %}
<link href="{% sass_src 'sass/project.scss' %}" rel="stylesheet">
{% endblock %}
{% block head-extra %}{% endblock head-extra %}
<script type="application/ld+json">
{
"@context": "http://schema.org",
@ -45,7 +46,7 @@
"@type": "PostalAddress",
"streetAddress": "Maidstone Hackspace, Maidstone Community Support Centre",
"addressLocality": "Maidstone",
"addressRegion": "FLKent",
"addressRegion": "Kent",
"postalCode": "ME14 1HH",
"addressCountry": "UK"
},
@ -66,6 +67,7 @@
</head>
<body>
<div>
<nav class="navbar navbar-toggleable-md navbar-inverse bg-inverse mb-4">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"

View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load sass_tags %}
{% load static i18n %}
{% block title %}Members{% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'spirit/stylesheets/styles.all.min.css' %}">
<link href="{% sass_src 'sass/project.scss' %}" rel="stylesheet">
{{ super.block }}
{% endblock css %}
{% block head-extra %}
<script src="{% static "spirit/scripts/all.min.js" %}"></script>
<script>
$( document ).ready(function() {
$.tab();
$( 'a.js-post' ).postify( {
csrfToken: "{{ csrf_token }}",
} );
$('.js-messages').messages();
{% if user.is_authenticated %}
$.notification( {
notificationUrl: "{% url "spirit:topic:notification:index-ajax" %}",
notificationListUrl: "{% url "spirit:topic:notification:index-unread" %}",
mentionTxt: "{% trans "{user} has mention you on {topic}" %}",
commentTxt: "{% trans "{user} has commented on {topic}" %}",
showAll: "{% trans "Show all" %}",
empty: "{% trans "No new notifications, yet" %}",
unread: "{% trans "unread" %}",
} );
{% endif %}
});
</script>
{% endblock head-extra %}
{% block content %}
<div class="container">
<h2>Users</h2>
<div class="list-group">
{% for user in user_list %}
<a href="{% url 'users:detail' user.username %}" class="list-group-item">
<h4 class="list-group-item-heading">{{ user.username }}</h4>
</a>
{% endfor %}
</div>
</div>
{% endblock content %}

View File

@ -2,6 +2,7 @@
{% load i18n %}
{% load static %}
{% load crispy_forms_tags %}
{% load socialaccount %}
{% block title %}User: {{ object.username }}{% endblock %}
@ -15,14 +16,22 @@
<p>{{ user.name }}</p>
<p>{{ user.email }}</p>
<p>Last login {{ user.last_login}}</p>
<p>Member since</p>
<p>Description: {{ blurb.description }}</p>
<p>Skills: {{ blurb.description }}</p>
{% if blurb.description %}
<p>Description: {{ blurb.description }}</p>
{% endif %}
{% if blurb.skills %}
<p>Skills: {{ blurb.skills }}</p>
{% endif %}
<h3>Membership status</h3>
<p>Membership Status: {{ membership.get_status }}</p>
<p>Last Payment: {{membership.date}}</p>
<p>Amount: &pound;{{membership.payment}}</p>
{% if membership %}
<h3>Membership status</h3>
<p>Member since {{membership.date}}</p>
<p>Membership Status: {{ membership.get_status }}</p>
<p>Last Payment: {{membership.date}}</p>
<p>Amount: &pound;{{membership.payment}}</p>
{% else %}
You are not currently a member consider signing up.
{% endif %}
</div>
<div class="col-md-6">
{% if membership.get_status %}
@ -63,11 +72,27 @@
<div class="row">
<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 'account_email' %}" role="button">E-Mail</a>
</p>
<!-- Your Stuff: Custom user template urls -->
</div>
</div>
<div class="row">
<div class="col-sm-12">
<p>
<a class="btn btn-primary" href="{% provider_login_url "google" process="connect" %}">Link your Google account</a>
</p>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<p>
<a class="btn btn-primary" href="{% provider_login_url "github" process="connect" %}">Link your Github account</a>
</p>
</div>
</div>
<!-- End Action buttons -->
{% endif %}

View File

@ -11,7 +11,7 @@ from django.urls import reverse
from django.conf.urls import url
from .models import User, Membership, MEMBERSHIP_STATUS_CHOICES
from mhackspace.subscriptions.management.commands.refresh_subscriptions import update_subscriptions
from mhackspace.subscriptions.management.commands.update_membership_status import update_subscriptions
class MyUserChangeForm(UserChangeForm):
@ -41,7 +41,7 @@ class MyUserAdmin(AuthUserAdmin):
form = MyUserChangeForm
add_form = MyUserCreationForm
fieldsets = (
('User Profile', {'fields': ('name', 'image')}),
('User Profile', {'fields': ('name', '_image')}),
) + AuthUserAdmin.fieldsets
list_display = ('username', 'name', 'is_superuser')
search_fields = ['name']

View File

@ -11,17 +11,28 @@ from stdimage.models import StdImageField
class User(AbstractUser):
name = models.CharField(_('Name of User'), blank=True, max_length=255)
public = models.BooleanField(default=False, help_text='If the users email is public on post feeds')
image = StdImageField(
public = models.BooleanField(
default=False, help_text='If the users email is public on post feeds')
_image = StdImageField(
upload_to='avatars/',
blank=True,
null=True,
db_column='image',
variations={
'profile': {
"width": 256,
"height": 256,
"crop": True}})
# https://github.com/pennersr/django-allauth/issues/520
@property
def image(self):
return self._image
@image.setter
def image(self, value):
self._image = value
def __str__(self):
return self.username
@ -50,6 +61,7 @@ MEMBERSHIP_STRING = {
}
MEMBERSHIP_STATUS = {
'signup': 0, # This means the user has not completed signup
'active': 1,
'cancelled': 2
}

View File

@ -26,6 +26,7 @@ class UserDetailView(LoginRequiredMixin, DetailView):
context['membership_form'] = MembershipJoinForm(initial={'amount': 20.00})
return context
class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False
@ -35,7 +36,7 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
class UserUpdateView(LoginRequiredMixin, UpdateView):
fields = ['name', 'image', ]
fields = ['name', '_image', ]
model = User
# send the user back to their own page after a successful update

View File

@ -52,7 +52,7 @@ lxml==3.7.3
# Your custom requirements go here
mock==2.0.0
gocardless
gocardless_pro
braintree==3.37.2
django-autofixture==0.12.1
@ -62,5 +62,10 @@ git+https://github.com/olymk2/scaffold.git
djangorestframework==3.6.3
django-filter==1.0.2
#git+https://github.com/olymk2/dracos-markdown-editor.git
draceditor==1.1.8
# django-spirit
django-djconfig
django-haystack
git+https://github.com/nitely/Spirit.git
# git+https://github.com/olymk2/django-xforwardedfor-middleware.git
django-xforwardedfor-middleware==2.0

View File

@ -11,3 +11,10 @@ factory-boy==2.8.1
# pytest
pytest-django==3.1.2
pytest-sugar==0.8.0
django-test-plus==1.0.18
# improved REPL
ipdb==0.10.3
pytest-django==3.1.2
pytest-sugar==0.8.0

View File

@ -28,6 +28,7 @@ services:
env_file: .env
volumes:
- .:/app
- ./compose/data/logs:/var/log/gunicorn
- sockets:/data/sockets
node: