diff --git a/compose/data/cache/README.org b/compose/data/cache/README.org new file mode 100644 index 0000000..187c56e --- /dev/null +++ b/compose/data/cache/README.org @@ -0,0 +1,3 @@ +* cache folder + +Store things like pip cache here so wee dont have to download every time wee build. diff --git a/compose/data/logs/README.org b/compose/data/logs/README.org new file mode 100644 index 0000000..7c11630 --- /dev/null +++ b/compose/data/logs/README.org @@ -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 diff --git a/config/settings/common.py b/config/settings/common.py index cdf63f2..5c86d97 100644 --- a/config/settings/common.py +++ b/config/settings/common.py @@ -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', ], }, }, diff --git a/config/settings/local.py b/config/settings/local.py index 17249d1..533b8a4 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -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' diff --git a/config/settings/production.py b/config/settings/production.py index 6522b43..abe0ba9 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -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 } } diff --git a/config/settings/stage.py b/config/settings/stage.py index cd6a73c..2fff117 100644 --- a/config/settings/stage.py +++ b/config/settings/stage.py @@ -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 } } diff --git a/config/settings/test.py b/config/settings/test.py index 8abc69f..98daf9b 100644 --- a/config/settings/test.py +++ b/config/settings/test.py @@ -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 } } diff --git a/config/urls.py b/config/urls.py index 6b246b6..a16ed4f 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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. diff --git a/dev.yml b/dev.yml index 8d44769..d998514 100644 --- a/dev.yml +++ b/dev.yml @@ -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: diff --git a/live.yml b/live.yml index b8f27cc..2614706 100644 --- a/live.yml +++ b/live.yml @@ -28,6 +28,7 @@ services: env_file: .env volumes: - .:/app + - ./compose/data/logs:/var/log/gunicorn - sockets:/data/sockets node: diff --git a/mhackspace/base/management/commands/generate_test_data.py b/mhackspace/base/management/commands/generate_test_data.py index 6a531b9..a3b8a8d 100644 --- a/mhackspace/base/management/commands/generate_test_data.py +++ b/mhackspace/base/management/commands/generate_test_data.py @@ -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( diff --git a/mhackspace/static/sass/components/_header.scss b/mhackspace/static/sass/components/_header.scss index 321f3ca..f450b0a 100644 --- a/mhackspace/static/sass/components/_header.scss +++ b/mhackspace/static/sass/components/_header.scss @@ -6,3 +6,11 @@ transform: rotate(360deg); } } + +body { + max-width: 100%; +} + +.navbar-brand { + width:100%; +} diff --git a/mhackspace/subscriptions/helper.py b/mhackspace/subscriptions/helper.py new file mode 100644 index 0000000..434bc63 --- /dev/null +++ b/mhackspace/subscriptions/helper.py @@ -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 diff --git a/mhackspace/subscriptions/management/commands/list_subscription_payments.py b/mhackspace/subscriptions/management/commands/list_subscription_payments.py index fc575d6..a6ea59c 100644 --- a/mhackspace/subscriptions/management/commands/list_subscription_payments.py +++ b/mhackspace/subscriptions/management/commands/list_subscription_payments.py @@ -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])))) diff --git a/mhackspace/subscriptions/management/commands/refresh_subscriptions.py b/mhackspace/subscriptions/management/commands/update_membership_status.py similarity index 70% rename from mhackspace/subscriptions/management/commands/refresh_subscriptions.py rename to mhackspace/subscriptions/management/commands/update_membership_status.py index 24cd193..c88901e 100644 --- a/mhackspace/subscriptions/management/commands/refresh_subscriptions.py +++ b/mhackspace/subscriptions/management/commands/update_membership_status.py @@ -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) diff --git a/mhackspace/subscriptions/payments.py b/mhackspace/subscriptions/payments.py index 723f29a..5c4c864 100644 --- a/mhackspace/subscriptions/payments.py +++ b/mhackspace/subscriptions/payments.py @@ -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 """ diff --git a/mhackspace/subscriptions/views.py b/mhackspace/subscriptions/views.py index aca1504..4529621 100644 --- a/mhackspace/subscriptions/views.py +++ b/mhackspace/subscriptions/views.py @@ -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) diff --git a/mhackspace/templates/base.html b/mhackspace/templates/base.html index 3dba8a6..b9f4fa3 100644 --- a/mhackspace/templates/base.html +++ b/mhackspace/templates/base.html @@ -27,11 +27,12 @@ + {% block css %} - {% endblock %} + {% block head-extra %}{% endblock head-extra %} + + +{% endblock head-extra %} + +{% block content %} +
+

Users

+ +
+ {% for user in user_list %} + +

{{ user.username }}

+
+ {% endfor %} +
+
+{% endblock content %} diff --git a/mhackspace/templates/users/user_detail.html b/mhackspace/templates/users/user_detail.html index a39ff54..9e354ea 100644 --- a/mhackspace/templates/users/user_detail.html +++ b/mhackspace/templates/users/user_detail.html @@ -2,6 +2,7 @@ {% load i18n %} {% load static %} {% load crispy_forms_tags %} +{% load socialaccount %} {% block title %}User: {{ object.username }}{% endblock %} @@ -15,14 +16,22 @@

{{ user.name }}

{{ user.email }}

Last login {{ user.last_login}}

-

Member since

-

Description: {{ blurb.description }}

-

Skills: {{ blurb.description }}

+ {% if blurb.description %} +

Description: {{ blurb.description }}

+ {% endif %} + {% if blurb.skills %} +

Skills: {{ blurb.skills }}

+ {% endif %} -

Membership status

-

Membership Status: {{ membership.get_status }}

-

Last Payment: {{membership.date}}

-

Amount: £{{membership.payment}}

+ {% if membership %} +

Membership status

+

Member since {{membership.date}}

+

Membership Status: {{ membership.get_status }}

+

Last Payment: {{membership.date}}

+

Amount: £{{membership.payment}}

+ {% else %} + You are not currently a member consider signing up. + {% endif %}
{% if membership.get_status %} @@ -63,11 +72,27 @@
+
+
+ +
+
+
{% endif %} diff --git a/mhackspace/users/admin.py b/mhackspace/users/admin.py index 45402b0..ba7ca58 100644 --- a/mhackspace/users/admin.py +++ b/mhackspace/users/admin.py @@ -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'] diff --git a/mhackspace/users/models.py b/mhackspace/users/models.py index 8b4d4de..1f0808a 100644 --- a/mhackspace/users/models.py +++ b/mhackspace/users/models.py @@ -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 } diff --git a/mhackspace/users/views.py b/mhackspace/users/views.py index 518d263..c124721 100644 --- a/mhackspace/users/views.py +++ b/mhackspace/users/views.py @@ -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 diff --git a/requirements/base.txt b/requirements/base.txt index ff447af..2e870ec 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -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 diff --git a/requirements/test.txt b/requirements/test.txt index 48dd017..76842aa 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -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 diff --git a/stage.yml b/stage.yml index 6d237b7..588548c 100644 --- a/stage.yml +++ b/stage.yml @@ -28,6 +28,7 @@ services: env_file: .env volumes: - .:/app + - ./compose/data/logs:/var/log/gunicorn - sockets:/data/sockets node: