Initial profile management page

This commit is contained in:
Oliver Marks 2017-01-06 20:35:56 +00:00
parent 2cef5bf87a
commit 9abd81245e
17 changed files with 218 additions and 13 deletions

41
.drone.yml Normal file
View File

@ -0,0 +1,41 @@
pipeline:
backend:
commands:
- python manage.py test
#volumes:
# postgres_data_dev: {}
# postgres_backup_dev: {}
services:
postgres:
build: ./compose/postgres
# volumes:
# - postgres_data_dev:/var/lib/postgresql/data
# - postgres_backup_dev:/backups
environment:
- POSTGRES_USER=mhackspace
# django:
# build:
# context: .
# dockerfile: ./compose/django/Dockerfile-dev
# command: /start-dev.sh
# depends_on:
# - postgres
# environment:
# - POSTGRES_USER=mhackspace
# - USE_DOCKER=yes
# volumes:
# - .:/app
# ports:
# - "8180:8000"
# links:
# - postgres
# - mailhog
mailhog:
image: mailhog/mailhog
ports:
- "8125:8025"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
media/*

View File

@ -40,3 +40,11 @@ docker-compose -f dev.yml run django python manage.py migrate
#+BEGIN_SRC sh #+BEGIN_SRC sh
docker-compose -f dev.yml run django python manage.py createsuperuser docker-compose -f dev.yml run django python manage.py createsuperuser
#+END_SRC #+END_SRC
** Migrations / Managing default data
If you want to export some data you entered into the admin area you can use =dumpdata= and =loaddata= to export and import.
#+BEGIN_SRC sh
docker-compose -fdev.yml run django python manage.py dumpdata feeds > mhackspace/feeds/fixtures/default.json
#+END_SRC

View File

@ -45,10 +45,10 @@ THIRD_PARTY_APPS = (
# Apps specific for this project go here. # Apps specific for this project go here.
LOCAL_APPS = ( LOCAL_APPS = (
# custom users app # custom users app
# Your stuff: custom apps go here
'mhackspace.users.apps.UsersConfig', 'mhackspace.users.apps.UsersConfig',
'mhackspace.feeds', 'mhackspace.feeds',
'mhackspace.contact', 'mhackspace.contact',
# Your stuff: custom apps go here
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps

View File

@ -17,6 +17,7 @@ urlpatterns = [
url(r'^contact/$', contact, name='contact'), url(r'^contact/$', contact, name='contact'),
# need to be logged in for these urls
# Django Admin, use {% url 'admin:index' %} # Django Admin, use {% url 'admin:index' %}
url(settings.ADMIN_URL, admin.site.urls), url(settings.ADMIN_URL, admin.site.urls),

View File

@ -34,5 +34,5 @@ services:
mailhog: mailhog:
image: mailhog/mailhog image: mailhog/mailhog
ports: ports:
- "8025:8125" - "8125:8025"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -28,6 +28,9 @@
<body> <body>
<div class="m-b-1"> <div class="m-b-1">
<a id="mini_logo" href="/login">
<img src="/static/images/hackspace.png" class="mini-logo">
</a>
<nav class="navbar navbar-dark navbar-static-top bg-inverse"> <nav class="navbar navbar-dark navbar-static-top bg-inverse">
<div class="container"> <div class="container">
<a class="navbar-brand" href="/">Maidstone Hackspace</a> <a class="navbar-brand" href="/">Maidstone Hackspace</a>

View File

@ -6,6 +6,32 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<h2>Profile</h2>
<div class="row">
<div class="col-md-6">
<img src="{{ MEDIA_URL }}{{ user.image }}" alt="Profile image"/>
<p>{{ user.username }}</p>
<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>
</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>
<a href="/profile/membership/cancel">Cancel Membership</a>
</div>
</div>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">

View File

@ -5,9 +5,10 @@
{% block content %} {% block content %}
<h1>{{ user.username }}</h1> <h1>{{ user.username }}</h1>
<form class="form-horizontal" method="post" action="{% url 'users:update' %}"> <form class="form-horizontal" method="post" action="{% url 'users:update' %}" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
{{ form_blurb|crispy }}
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<button type="submit" class="btn">Update</button> <button type="submit" class="btn">Update</button>

View File

@ -36,7 +36,7 @@ class MyUserAdmin(AuthUserAdmin):
form = MyUserChangeForm form = MyUserChangeForm
add_form = MyUserCreationForm add_form = MyUserCreationForm
fieldsets = ( fieldsets = (
('User Profile', {'fields': ('name',)}), ('User Profile', {'fields': ('name', 'image')}),
) + AuthUserAdmin.fieldsets ) + AuthUserAdmin.fieldsets
list_display = ('username', 'name', 'is_superuser') list_display = ('username', 'name', 'is_superuser')
search_fields = ['name'] search_fields = ['name']

10
mhackspace/users/forms.py Normal file
View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.forms import ModelForm
from .models import UserBlurb
class UserBlurbForm(ModelForm):
class Meta:
model = UserBlurb
exclude = ['user']

View File

@ -0,0 +1,36 @@
# -*- 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

@ -0,0 +1,20 @@
# -*- 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

@ -0,0 +1,24 @@
# -*- 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,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models from django.db import models
@ -10,13 +11,24 @@ from django.utils.translation import ugettext_lazy as _
@python_2_unicode_compatible @python_2_unicode_compatible
class User(AbstractUser): class User(AbstractUser):
# First Name and Last Name do not cover name patterns
# around the globe.
name = models.CharField(_('Name of User'), blank=True, max_length=255) name = models.CharField(_('Name of User'), blank=True, max_length=255)
image = models.ImageField(upload_to='avatars/', blank=True, null=True)
def __str__(self): def __str__(self):
return self.username return self.username
def get_absolute_url(self): def get_absolute_url(self):
return reverse('users:detail', kwargs={'username': self.username}) return reverse('users:detail', kwargs={'username': self.username})
class UserBlurb(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
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)
date = models.DateTimeField()
reference = models.CharField(max_length=255)

View File

@ -7,7 +7,10 @@ from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from .models import User from .models import User
from .models import UserBlurb
from .models import UserMembership
from .forms import UserBlurbForm
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(LoginRequiredMixin, DetailView):
model = User model = User
@ -15,6 +18,12 @@ class UserDetailView(LoginRequiredMixin, DetailView):
slug_field = 'username' slug_field = 'username'
slug_url_kwarg = 'username' slug_url_kwarg = 'username'
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()
return context
class UserRedirectView(LoginRequiredMixin, RedirectView): class UserRedirectView(LoginRequiredMixin, RedirectView):
permanent = False permanent = False
@ -25,21 +34,34 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
class UserUpdateView(LoginRequiredMixin, UpdateView): class UserUpdateView(LoginRequiredMixin, UpdateView):
fields = ['name', 'image', ]
fields = ['name', ]
# we already imported User in the view code above, remember?
model = User model = User
# send the user back to their own page after a successful update # send the user back to their own page after a successful update
def get_success_url(self): def get_success_url(self):
return reverse('users:detail', return reverse(
'users:detail',
kwargs={'username': self.request.user.username}) kwargs={'username': self.request.user.username})
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)
return context
def get_object(self): def get_object(self):
# Only get the User record for the user making the request # Only get the User record for the user making the request
return User.objects.get(username=self.request.user.username) 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)
if form_blurb.is_valid():
blurb_model = form_blurb.save(commit=False)
blurb_model.user = self.request.user
blurb_model.save()
return super(UserUpdateView, self).form_valid(form)
class UserListView(LoginRequiredMixin, ListView): class UserListView(LoginRequiredMixin, ListView):
model = User model = User