Cleaned up migrations, added refresh subscriptions command to update members status

This commit is contained in:
Oly 2017-01-28 22:34:47 +00:00
parent 6042d68b66
commit fb271941bb
21 changed files with 208 additions and 304 deletions

View File

@ -9,6 +9,10 @@ pipeline:
- cp -n env.example .env
- python manage.py test mhackspace --verbosity 2
deploy:
#volumes:
# postgres_data_dev: {}
# postgres_backup_dev: {}

View File

@ -23,36 +23,36 @@ services:
command: /gunicorn.sh
env_file: .env
nginx:
build: ./compose/nginx
depends_on:
- django
- certbot
# nginx:
# build: ./compose/nginx
# depends_on:
# - django
# - certbot
ports:
- "0.0.0.0:80:80"
# ports:
# - "0.0.0.0:80:80"
environment:
- MY_DOMAIN_NAME=maidstone-hackspace.org.uk
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
# environment:
# - MY_DOMAIN_NAME=maidstone-hackspace.org.uk
# ports:
# - "0.0.0.0:80:80"
# - "0.0.0.0:443:443"
# volumes:
# - /etc/letsencrypt:/etc/letsencrypt
# - /var/lib/letsencrypt:/var/lib/letsencrypt
certbot:
image: quay.io/letsencrypt/letsencrypt
command: bash -c "sleep 6 && certbot certonly -n --standalone -d maidstone-hackspace.org.uk --text --agree-tos --email support@maidstone-hackspace.org.uk --server https://acme-v01.api.letsencrypt.org/directory --rsa-key-size 4096 --verbose --keep-until-expiring --standalone-supported-challenges http-01"
entrypoint: ""
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
ports:
- "80"
- "443"
environment:
- TERM=xterm
# certbot:
# image: quay.io/letsencrypt/letsencrypt
# command: bash -c "sleep 6 && certbot certonly -n --standalone -d maidstone-hackspace.org.uk --text --agree-tos --email support@maidstone-hackspace.org.uk --server https://acme-v01.api.letsencrypt.org/directory --rsa-key-size 4096 --verbose --keep-until-expiring --standalone-supported-challenges http-01"
# entrypoint: ""
# volumes:
# - /etc/letsencrypt:/etc/letsencrypt
# - /var/lib/letsencrypt:/var/lib/letsencrypt
# ports:
# - "80"
# - "443"
# environment:
# - TERM=xterm
redis:

View File

@ -1,8 +1,12 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 14:04
# Generated by Django 1.10.5 on 2017-01-28 18:38
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration):
@ -13,14 +17,35 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('title', models.CharField(max_length=255)),
('original_image', models.URLField(blank=True, max_length=255, null=True)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('description', models.TextField()),
('displayed', models.BooleanField(default=True)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.CreateModel(
name='Feed',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.CharField(max_length=255)),
('home_url', models.URLField(verbose_name='Site Home Page')),
('feed_url', models.URLField(verbose_name='RSS Feed URL')),
('title', models.CharField(max_length=255)),
('author', models.CharField(max_length=255)),
('tags', models.CharField(max_length=255)),
('image', models.ImageField(upload_to='')),
('tags', models.CharField(blank=True, max_length=255)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('enabled', models.BooleanField(default=True)),
],
),
migrations.AddField(
model_name='article',
name='feed',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed'),
),
]

View File

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 20:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='id',
field=models.IntegerField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='feed',
name='image',
field=models.ImageField(blank=True, upload_to=''),
),
migrations.AlterField(
model_name='feed',
name='tags',
field=models.CharField(blank=True, max_length=255),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 20:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0002_auto_20170104_2033'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='id',
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-04 21:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feeds', '0003_auto_20170104_2035'),
]
operations = [
migrations.AddField(
model_name='feed',
name='enabled',
field=models.BooleanField(default=True),
),
]

View File

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 03:42
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration):
dependencies = [
('feeds', '0004_feed_enabled'),
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('title', models.CharField(max_length=255)),
('original_image', models.URLField(blank=True, max_length=255, null=True)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('description', models.TextField()),
('displayed', models.BooleanField(default=True)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.RemoveField(
model_name='feed',
name='url',
),
migrations.AddField(
model_name='feed',
name='feed_url',
field=models.URLField(default='http://thearduinoguy.org/?feed=rss2', verbose_name='RSS Feed URL'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='home_url',
field=models.URLField(default='http://thearduinoguy.org/', verbose_name='Site Home Page'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='title',
field=models.CharField(default='The Arduino Guy', max_length=255),
preserve_default=False,
),
migrations.AlterField(
model_name='feed',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='feed',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title')),
),
migrations.AddField(
model_name='article',
name='feed',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed'),
),
]

View File

@ -10,7 +10,7 @@ from mhackspace.users.models import User
class MemberListView(LoginRequiredMixin, ListView):
template_name = 'pages/members.html'
queryset = User.objects.prefetch_related('users', 'groups')
queryset = User.objects.prefetch_related('user', 'groups')
paginate_by = 10
def get_context_data(self, **kwargs):

View File

@ -24,4 +24,4 @@ class Command(BaseCommand):
for sub in provider.fetch_subscriptions():
self.stdout.write(
self.style.SUCCESS(
'\t{reference} - {amount} - {status} - {email}'.format(**sub)))
'\t{start_date} {reference} - {amount} - {status} - {email}'.format(**sub)))

View File

@ -0,0 +1,51 @@
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
from mhackspace.subscriptions.payments import select_provider
from mhackspace.users.models import Membership, User
class Command(BaseCommand):
help = 'Update user subscriptions'
def handle(self, *args, **options):
provider = select_provider('gocardless')
self.stdout.write(
self.style.NOTICE(
'== Gocardless subscriptions =='))
Membership.objects.all().delete()
subscriptions = []
group = Group.objects.get(name='member')
for sub in provider.fetch_subscriptions():
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.stdout.write(sub.get('status'))
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(),
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]))))
Membership.objects.bulk_create(subscriptions)

View File

@ -4,8 +4,8 @@
Members feed
<div class="row">
<div class="">Users {{ paginator.count }}</div>
<div class="">Members {{ total }}</div>
<div class="col">Users {{ paginator.count }}</div>
<div class="col">Members {{ total }}</div>
</div>
<div class="card-deck-wrapper">
@ -17,13 +17,14 @@ Members feed
<h4 class="card-title">{{ member.name }}</h4>
<p class="card-text">{{ member.users.description }}</p>
<p class="card-text">{{ member.users.skills }}</p>
<p class="card-text">{{ member.status }}</p>
{% for group in member.groups.all %}
{{ group.name }}
{% endfor %}
</div>
<div class="card-block">
<p class="card-text">{{ member.userblurb }}</p>
<p class="card-text">{{ member.blurb }}</p>
</div>
<div class="card-block">
<a href="{{ feed.url }}" class="card-link">View Original</a>

View File

@ -5,7 +5,21 @@
{% block content %}
<div class="container">
<style>
#membercard {
color: #000;
background-color: #8C50A5;
border-radius: 14px;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.5);
background-image: url('/static/images/membership_card_background.png');
width: 430px;
height: 240px;
margin-left: 20px;
text-shadow: 1px 1px #FFF;
position: relative;
float: right;
}
</style>
<h2>Profile</h2>
<div class="row">
<div class="col-md-6">
@ -18,13 +32,14 @@
<p>Member since</p>
<p>Description: {{ blurb.description }}</p>
<p>Skills: {{ blurb.description }}</p>
<p>Membership Status: {{ membership.get_status }}</p>
</div>
<div class="col-md-6">
<div id="membercard" class="registered">
<div class="date">Joined </div>
<div class="container">
<div class="middle">
<p>MHS{{ user.id|stringformat:"05d" }}</p><p>Change me</p>
<p>MHS{{ user.id|stringformat:"05d" }}</p><p>{{user.name}}{{user.last_name}}</p>
<a href="/profile/membership/cancel">Cancel Membership</a>
</div>
</div>

View File

@ -2,9 +2,9 @@
from django.db import models
from django.forms import ModelForm
from .models import UserBlurb
from .models import Blurb
class UserBlurbForm(ModelForm):
class BlurbForm(ModelForm):
class Meta:
model = UserBlurb
model = Blurb
exclude = ['user']

View File

@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-23 04:36
# Generated by Django 1.10.5 on 2017-01-28 19:46
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
class Migration(migrations.Migration):
@ -32,16 +35,38 @@ class Migration(migrations.Migration):
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to='avatars/')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name_plural': 'users',
'verbose_name': 'user',
'abstract': False,
'verbose_name_plural': 'users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Blurb',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('skills', models.CharField(max_length=255)),
('description', models.TextField()),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Membership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment', models.DecimalField(decimal_places=2, default=0.0, max_digits=6)),
('date', models.DateTimeField()),
('reference', models.CharField(max_length=255)),
('status', models.PositiveSmallIntegerField(default=0)),
('email', models.CharField(max_length=255)),
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 08:53
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', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Membership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment', models.DecimalField(decimal_places=2, max_digits=6)),
('date', models.DateTimeField()),
('reference', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserBlurb',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('skills', models.CharField(max_length=255)),
('description', models.TextField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 19:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_membership_userblurb'),
]
operations = [
migrations.AddField(
model_name='user',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='/upload/avatars'),
),
]

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 20:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_user_image'),
]
operations = [
migrations.RenameModel(
old_name='Membership',
new_name='UserMembership',
),
migrations.AlterField(
model_name='user',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='avatars/'),
),
]

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 00:50
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20170106_2030'),
]
operations = [
migrations.AlterField(
model_name='user',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to='avatars/'),
),
migrations.AlterField(
model_name='userblurb',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -29,14 +29,44 @@ class User(AbstractUser):
return reverse('users:detail', kwargs={'username': self.username})
class UserBlurb(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='users')
class Blurb(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='+')
skills = models.CharField(max_length=255)
description = models.TextField()
class UserMembership(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
payment = models.DecimalField(max_digits=6, decimal_places=2)
MEMBERSHIP_STRING = {
0: 'Non Member',
1: 'Member',
3: 'Membership Expired'
}
MEMBERSHIP_STATUS = {
'active': 1,
'cancelled': 2
}
class Membership(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True, blank=True,
default=None,
related_name='user'
)
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)
email = models.CharField(max_length=255)
@property
def get_status(self):
return MEMBERSHIP_STRING[self.status]
def lookup_status(name):
if not name:
return 0
return MEMBERSHIP_STATUS.get(name.lower(), 0)
def __str__(self):
return self.reference

View File

@ -7,10 +7,10 @@ from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import User
from .models import UserBlurb
from .models import UserMembership
from .models import Blurb
from .models import Membership
from .forms import UserBlurbForm
from .forms import BlurbForm
class UserDetailView(LoginRequiredMixin, DetailView):
model = User
@ -21,8 +21,8 @@ class UserDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects
context = super(UserDetailView, self).get_context_data(**kwargs)
context['blurb'] = UserBlurb.objects.filter(user=self.get_object()).first()
context['membership'] = UserMembership.objects.filter(user=self.get_object()).first()
context['blurb'] = Blurb.objects.filter(user=self.get_object()).first()
context['membership'] = Membership.objects.filter(user=self.get_object()).first()
return context
class UserRedirectView(LoginRequiredMixin, RedirectView):
@ -45,8 +45,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
def get_context_data(self, **kwargs):
context = super(UserUpdateView, self).get_context_data(**kwargs)
profile = UserBlurb.objects.filter(user=self.get_object()).first()
context['form_blurb'] = UserBlurbForm(instance=profile)
profile = Blurb.objects.filter(user=self.get_object()).first()
context['form_blurb'] = BlurbForm(instance=profile)
return context
def get_object(self):
@ -54,8 +54,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return User.objects.get(username=self.request.user.username)
def form_valid(self, form):
profile = UserBlurb.objects.filter(user=self.get_object()).first()
form_blurb = UserBlurbForm(self.request.POST, instance=profile)
profile = Blurb.objects.filter(user=self.get_object()).first()
form_blurb = BlurbForm(self.request.POST, instance=profile)
if form_blurb.is_valid():
blurb_model = form_blurb.save(commit=False)
blurb_model.user = self.request.user