Add Settings model extending User.

This commit is contained in:
Christopher Charbonneau Wells 2017-11-11 17:27:42 -05:00
parent f9f6ea5e88
commit 60ac9a7f5d
12 changed files with 291 additions and 52 deletions

27
babybuddy/admin.py Normal file
View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from babybuddy import models
class SettingsInline(admin.StackedInline):
model = models.Settings
verbose_name_plural = 'Settings'
can_delete = False
fieldsets = (
('Dashboard', {
'fields': ('dashboard_refresh_rate',)
}),
)
class UserAdmin(BaseUserAdmin):
inlines = (SettingsInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

19
babybuddy/forms.py Normal file
View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.contrib.auth.models import User
from .models import Settings
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
class UserSettingsForm(forms.ModelForm):
class Meta:
model = Settings
fields = ['dashboard_refresh_rate']

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-12 17:05
from __future__ import unicode_literals
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Settings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('dashboard_refresh_rate', models.DurationField(blank=True, choices=[(None, 'disabled'), (datetime.timedelta(0, 60), '1 min.'), (datetime.timedelta(0, 120), '2 min.'), (datetime.timedelta(0, 180), '3 min.'), (datetime.timedelta(0, 240), '4 min.'), (datetime.timedelta(0, 300), '5 min.'), (datetime.timedelta(0, 600), '10 min.'), (datetime.timedelta(0, 900), '15 min.'), (datetime.timedelta(0, 1800), '30 min.')], default=datetime.timedelta(0, 60), null=True, verbose_name='Refresh rate')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

53
babybuddy/models.py Normal file
View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.timezone import timedelta
class Settings(models.Model):
user = models.OneToOneField(User)
dashboard_refresh_rate = models.DurationField(
verbose_name='Refresh rate',
blank=True,
null=True,
default=timedelta(minutes=1),
choices=[
(None, 'disabled'),
(timedelta(minutes=1), '1 min.'),
(timedelta(minutes=2), '2 min.'),
(timedelta(minutes=3), '3 min.'),
(timedelta(minutes=4), '4 min.'),
(timedelta(minutes=5), '5 min.'),
(timedelta(minutes=10), '10 min.'),
(timedelta(minutes=15), '15 min.'),
(timedelta(minutes=30), '30 min.'),
])
def __str__(self):
return '{}\'s Settings'.format(self.user)
@property
def dashboard_refresh_rate_milliseconds(self):
"""
Convert seconds to milliseconds to be used in a Javascript setInterval
function call.
:return: the refresh rate in milliseconds or None.
"""
if self.dashboard_refresh_rate:
return self.dashboard_refresh_rate.seconds * 1000
return None
@receiver(post_save, sender=User)
def create_user_settings(sender, instance, created, **kwargs):
if created:
Settings.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_settings(sender, instance, **kwargs):
instance.settings.save()

View File

@ -16,50 +16,7 @@
{% endif %} {% endif %}
{% for field in form %} {% for field in form %}
<div class="form-group row"> <div class="form-group row">
<label for="id_{{ field.name }}" class="col-sm-1 col-form-label"> {% include 'babybuddy/form_field.html' %}
{% if field|field_type != "booleanfield" %}
{{ field.label }}
{% endif %}
</label>
<div class="col-sm-11">
{% if field|field_type == "booleanfield" %}
<div class="form-check">
<label for="id_{{ field.name }}" class="form-check-label">
{% if field.errors %}
{{ field|add_class:"form-check-input is-invalid" }}
{% else %}
{{ field|add_class:"form-check-input" }}
{% endif %}
{{ field.label }}
</label>
</div>
{% elif field|field_type == "datetimefield" or field|field_type == "datefield" %}
<div class="form-group">
<div class="input-group date" id="datetimepicker_{{ field.name }}" data-target-input="nearest">
<div class="input-group-addon" data-target="#datetimepicker_{{ field.name }}" data-toggle="datetimepicker">
<i class="fa fa-calendar"></i>
</div>
{% if field.errors %}
{{ field|add_class:"form-control is-invalid" }}
{% else %}
{{ field|add_class:"form-control" }}
{% endif %}
</div>
</div>
{% else %}
{% if field.errors %}
{{ field|add_class:"form-control is-invalid" }}
{% else %}
{{ field|add_class:"form-control" }}
{% endif %}
{% endif %}
{% if field.help_text %}
<div class="help-block"><small>{{ field.help_text }}</small></div>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">{% for error in field.errors %}{{ error }}{% endfor %}</div>
{% endif %}
</div>
</div> </div>
{% endfor %} {% endfor %}
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>

View File

@ -0,0 +1,46 @@
{% load widget_tweaks %}
<label for="id_{{ field.name }}" class="col-sm-2 col-form-label">
{% if field|field_type != "booleanfield" %}
{{ field.label }}
{% endif %}
</label>
<div class="col-sm-10">
{% if field|field_type == "booleanfield" %}
<div class="form-check">
<label for="id_{{ field.name }}" class="form-check-label">
{% if field.errors %}
{{ field|add_class:"form-check-input is-invalid" }}
{% else %}
{{ field|add_class:"form-check-input" }}
{% endif %}
{{ field.label }}
</label>
</div>
{% elif field|field_type == "datetimefield" or field|field_type == "datefield" %}
<div class="form-group">
<div class="input-group date" id="datetimepicker_{{ field.name }}" data-target-input="nearest">
<div class="input-group-addon" data-target="#datetimepicker_{{ field.name }}" data-toggle="datetimepicker">
<i class="fa fa-calendar"></i>
</div>
{% if field.errors %}
{{ field|add_class:"form-control is-invalid" }}
{% else %}
{{ field|add_class:"form-control" }}
{% endif %}
</div>
</div>
{% else %}
{% if field.errors %}
{{ field|add_class:"form-control is-invalid" }}
{% else %}
{{ field|add_class:"form-control" }}
{% endif %}
{% endif %}
{% if field.help_text %}
<div class="help-block"><small>{{ field.help_text }}</small></div>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">{% for error in field.errors %}{{ error }}{% endfor %}</div>
{% endif %}
</div>

View File

@ -173,16 +173,17 @@
aria-expanded="false"><i class="icon icon-user" aria-hidden="true"></i> {{ request.user }} aria-expanded="false"><i class="icon icon-user" aria-hidden="true"></i> {{ request.user }}
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-user-menu-link"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-user-menu-link">
<a href="{% url 'user-settings' %}" class="dropdown-item">Settings</a>
<div class="dropdown-divider"></div>
<a href="{% url 'api:api-root' %}" <a href="{% url 'api:api-root' %}"
class="dropdown-item" class="dropdown-item"
target="_blank">API Browser</a> target="_blank">API Browser</a>
{% if request.user.is_staff %} {% if request.user.is_staff %}
<a href="{% url 'admin:index' %}" <a href="{% url 'admin:index' %}"
class="dropdown-item" class="dropdown-item"
target="_blank">Admin</a> target="_blank">Site Admin</a>
{% endif %} {% endif %}
<div class="dropdown-divider"></div>
<a href="{% url 'logout' %}" <a href="{% url 'logout' %}"
class="dropdown-item">Logout</a> class="dropdown-item">Logout</a>
</div> </div>

View File

@ -0,0 +1,56 @@
{% extends 'babybuddy/page.html' %}
{% load widget_tweaks %}
{% block title %}User Settings{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item">User</li>
<li class="breadcrumb-item active">Settings</li>
{% endblock %}
{% block content %}
<h1>User Settings</h1>
<div class="container-fluid">
<form role="form" method="post">
{% csrf_token %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> {{ error }}
</div>
{% endfor %}
{% elif form.errors %}
<div class="alert alert-danger" role="alert">
<strong>Error:</strong> Some fields have errors. See below for details.
</div>
{% endif %}
<fieldset>
<legend>User Profile</legend>
<div class="form-group row">
{% with form_user.first_name as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
<div class="form-group row">
{% with form_user.last_name as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
<div class="form-group row">
{% with form_user.email as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
</fieldset>
<fieldset>
<legend>Dashboard</legend>
<div class="form-group row">
{% with form_settings.dashboard_refresh_rate as field %}
{% include 'babybuddy/form_field.html' %}
{% endwith %}
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -17,6 +17,8 @@ urlpatterns = [
url(r'^$', views.RootRouter.as_view(), name='root-router'), url(r'^$', views.RootRouter.as_view(), name='root-router'),
url(r'^welcome/$', views.Welcome.as_view(), name='welcome'), url(r'^welcome/$', views.Welcome.as_view(), name='welcome'),
url(r'^user/settings/$', views.UserSettings.as_view(),
name='user-settings'),
url(r'', include('api.urls', namespace='api')), url(r'', include('api.urls', namespace='api')),
url(r'', include('core.urls')), url(r'', include('core.urls')),

View File

@ -2,13 +2,22 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse
from django.views.generic import View
from django.views.generic.base import TemplateView, RedirectView from django.views.generic.base import TemplateView, RedirectView
from .forms import UserForm, UserSettingsForm
from core.models import Child from core.models import Child
class RootRouter(LoginRequiredMixin, RedirectView): class RootRouter(LoginRequiredMixin, RedirectView):
"""
Redirects to the welcome page if no children are in the database, a child
dashboard if only one child is in the database, and the dashboard page if
more than one child is in the database.
"""
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
children = Child.objects.count() children = Child.objects.count()
if children == 0: if children == 0:
@ -21,5 +30,44 @@ class RootRouter(LoginRequiredMixin, RedirectView):
return super(RootRouter, self).get_redirect_url(self, *args, **kwargs) return super(RootRouter, self).get_redirect_url(self, *args, **kwargs)
class UserSettings(LoginRequiredMixin, View):
"""
Handles both the User and Settings models.
Based on this SO answer: https://stackoverflow.com/a/45056835.
"""
form_user_class = UserForm
form_settings_class = UserSettingsForm
template_name = 'babybuddy/user_settings_form.html'
def get(self, request):
return render(request, self.template_name, {
'form_user': self.form_user_class(instance=request.user),
'form_settings': self.form_settings_class(
instance=request.user.settings)
})
def post(self, request):
form_user = self.form_user_class(
instance=request.user,
data=request.POST)
form_settings = self.form_settings_class(
instance=request.user.settings,
data=request.POST)
if form_user.is_valid() and form_settings.is_valid():
user = form_user.save(commit=False)
user_settings = form_settings.save(commit=False)
user.settings = user_settings
user.save()
return redirect('user-settings')
return render(request, self.template_name, {
'user_form': form_user,
'settings_form': form_settings
})
class Welcome(LoginRequiredMixin, TemplateView): class Welcome(LoginRequiredMixin, TemplateView):
"""
Basic introduction to Baby Buddy (meant to be shown when no data is in the
database).
"""
template_name = 'babybuddy/welcome.html' template_name = 'babybuddy/welcome.html'

View File

@ -9,7 +9,7 @@ BabyBuddy.Dashboard = function ($) {
var lastUpdate = moment(); var lastUpdate = moment();
var Dashboard = { var Dashboard = {
watch: function(element_id) { watch: function(element_id, refresh_rate) {
dashboardElement = $('#' + element_id); dashboardElement = $('#' + element_id);
if (dashboardElement.length == 0) { if (dashboardElement.length == 0) {
@ -17,7 +17,7 @@ BabyBuddy.Dashboard = function ($) {
return false; return false;
} }
runIntervalId = setInterval(this.update, 60000); runIntervalId = setInterval(this.update, refresh_rate);
Visibility.change(function (e, state) { Visibility.change(function (e, state) {
if (state == 'visible' && moment().diff(lastUpdate) > 60000) { if (state == 'visible' && moment().diff(lastUpdate) > 60000) {

View File

@ -31,7 +31,9 @@
{% endblock %} {% endblock %}
{% block javascript %} {% block javascript %}
{% if user.settings.dashboard_refresh_rate %}
<script type="application/javascript"> <script type="application/javascript">
BabyBuddy.Dashboard.watch('dashboard-child'); BabyBuddy.Dashboard.watch('dashboard-child', {{ user.settings.dashboard_refresh_rate_milliseconds }});
</script> </script>
{% endif %}
{% endblock %} {% endblock %}