mirror of https://github.com/snachodog/mybuddy.git
Add Settings model extending User.
This commit is contained in:
parent
f9f6ea5e88
commit
60ac9a7f5d
|
@ -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)
|
|
@ -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']
|
|
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -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()
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
|
@ -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')),
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 %}
|
Loading…
Reference in New Issue