Merge branch 'preferences' into 'master'

Feature: Userpreferences

See merge request oliver/ivatar!24
This commit is contained in:
Oliver Falk
2018-07-05 14:11:52 +02:00
12 changed files with 202 additions and 7 deletions

View File

@@ -16,6 +16,7 @@ from ivatar.settings import MIN_LENGTH_EMAIL, MAX_LENGTH_EMAIL
from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL from ivatar.settings import MIN_LENGTH_URL, MAX_LENGTH_URL
from . models import UnconfirmedEmail, ConfirmedEmail, Photo from . models import UnconfirmedEmail, ConfirmedEmail, Photo
from . models import UnconfirmedOpenId, ConfirmedOpenId from . models import UnconfirmedOpenId, ConfirmedOpenId
from . models import UserPreference
MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5 MAX_NUM_UNCONFIRMED_EMAILS_DEFAULT = 5
@@ -180,3 +181,16 @@ class AddOpenIDForm(forms.Form):
unconfirmed.save() unconfirmed.save()
return unconfirmed.pk return unconfirmed.pk
class UpdatePreferenceForm(forms.ModelForm):
'''
Form for updating user preferences
'''
class Meta:
'''
Meta class for UpdatePreferenceForm
'''
model = UserPreference
fields = ['theme']

View File

@@ -0,0 +1,35 @@
# Generated by Django 2.0.6 on 2018-07-04 12:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def add_preference_to_user(apps, schema_editor):
'''
Make sure all users have preferences set up
'''
from django.contrib.auth.models import User
UserPreference = apps.get_model('ivataraccount', 'UserPreference')
for u in User.objects.filter(userpreference=None):
p = UserPreference.objects.create(user_id=u.pk)
p.save()
class Migration(migrations.Migration):
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('ivataraccount', '0007_auto_20180627_0624'),
]
operations = [
migrations.CreateModel(
name='UserPreference',
fields=[
('theme', models.CharField(choices=[('default', 'Default theme'), ('clime', 'Climes theme')], default='default', max_length=10)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(add_preference_to_user),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-07-05 11:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ivataraccount', '0008_userpreference'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('default', 'Default theme'), ('clime', 'climes theme'), ('falko', 'falkos theme')], default='default', max_length=10),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.0.6 on 2018-07-05 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ivataraccount', '0009_auto_20180705_1152'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('default', 'Default theme'), ('falko', 'falkos theme')], default='default', max_length=10),
),
]

View File

@@ -57,6 +57,32 @@ def pil_format(image_type):
return None return None
class UserPreference(models.Model):
'''
Holds the user users preferences
'''
THEMES = (
('default', 'Default theme'),
# ('clime', 'climes theme'), # Not yet available
('falko', 'falkos theme'),
)
theme = models.CharField(
max_length=10,
choices=THEMES,
default='default',
)
user = models.OneToOneField(
User,
on_delete=models.deletion.CASCADE,
primary_key=True,
)
def __str__(self):
return '<UserPreference (%i) for %s>' % (self.pk, self.user)
class BaseAccountModel(models.Model): class BaseAccountModel(models.Model):
''' '''
Base, abstract model, holding fields we use in all cases Base, abstract model, holding fields we use in all cases

View File

@@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bootstrap4 %}
{% load static %}
{% block title %}{% trans 'Your Preferences' %}{% endblock title %}
{% block content %}
<div style="width:600px;">
<form method="post" name="check">{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">{% trans 'Update' %}</button>
<button type="cancel" class="btn btn-danger">{% trans 'Cancel' %}</button>
{% endbuttons %}
</form>
</div>
{% endblock content %}

View File

@@ -13,6 +13,7 @@ from . views import ImportPhotoView, RawImageView, DeletePhotoView
from . views import UploadPhotoView, AssignPhotoOpenIDView from . views import UploadPhotoView, AssignPhotoOpenIDView
from . views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView from . views import AddOpenIDView, RedirectOpenIDView, ConfirmOpenIDView
from . views import CropPhotoView from . views import CropPhotoView
from . views import UserPreferenceView
# Define URL patterns, self documenting # Define URL patterns, self documenting
# To see the fancy, colorful evaluation of these use: # To see the fancy, colorful evaluation of these use:
@@ -73,4 +74,5 @@ urlpatterns = [ # pylint: disable=invalid-name
DeletePhotoView.as_view(), name='delete_photo'), DeletePhotoView.as_view(), name='delete_photo'),
url(r'raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'), url(r'raw_image/(?P<pk>\d+)', RawImageView.as_view(), name='raw_image'),
url(r'crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'), url(r'crop_photo/(?P<pk>\d+)', CropPhotoView.as_view(), name='crop_photo'),
url(r'pref/$', UserPreferenceView.as_view(), name='user_preference'),
] ]

View File

@@ -5,7 +5,7 @@ from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages from django.contrib import messages
from django.views.generic.edit import FormView from django.views.generic.edit import FormView, UpdateView
from django.views.generic.base import View, TemplateView from django.views.generic.base import View, TemplateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
@@ -19,8 +19,10 @@ from openid import oidutil
from openid.consumer import consumer from openid.consumer import consumer
from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm from .forms import AddEmailForm, UploadPhotoForm, AddOpenIDForm
from .forms import UpdatePreferenceForm
from .models import UnconfirmedEmail, ConfirmedEmail, Photo from .models import UnconfirmedEmail, ConfirmedEmail, Photo
from .models import UnconfirmedOpenId, ConfirmedOpenId, DjangoOpenIDStore from .models import UnconfirmedOpenId, ConfirmedOpenId, DjangoOpenIDStore
from .models import UserPreference
from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE from ivatar.settings import MAX_NUM_PHOTOS, MAX_PHOTO_SIZE
@@ -561,3 +563,16 @@ class CropPhotoView(TemplateView):
pass # Ignore automatic assignment pass # Ignore automatic assignment
return photo.perform_crop(request, dimensions, email, openid) return photo.perform_crop(request, dimensions, email, openid)
@method_decorator(login_required, name='dispatch')
class UserPreferenceView(FormView, UpdateView):
'''
View class for user preferences view/update
'''
template_name = 'preferences.html'
model = UserPreference
form_class = UpdatePreferenceForm
success_url = reverse_lazy('user_preference')
def get_object(self):
return self.request.user.userpreference

View File

@@ -0,0 +1,41 @@
/* Main palette from http://www.colourlovers.com/palette/4581580/Dr._Hans_6 */
body {
background-color: #DEE8F1;
}
#site-name {
color: #675E57;
}
#site-branding {
color: #4F6384;
}
h1 {
color: #675E57;
}
h2 {
color: #4F6384;
}
#outer {
background-color: #9AB5D2;
}
a {
color: #675E57;
}
#content {
background-color: #FFFFFF;
}
#content a {
color: #4F6384;
}
.fas, .fab {
color: #675E57;
}

View File

@@ -40,7 +40,7 @@
<a href="{{ mailurl }}"> <a href="{{ mailurl }}">
<img src="{{ mailurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ mailurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px;"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px;">
<i class="fas fa-unlock" title="None-SSL connection (http)"></i> <i class="fas fa-unlock" title="None-SSL connection (http)"></i>
<br/> <br/>
<i class="fas fa-at" title="mail: {{ form.mail.value }}"></i> <i class="fas fa-at" title="mail: {{ form.mail.value }}"></i>
@@ -53,7 +53,7 @@
<a href="{{ mailurl_secure }}"> <a href="{{ mailurl_secure }}">
<img src="{{ mailurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ mailurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px;"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px;">
<i class="fas fa-lock" title="Secure connection (https)"></i> <i class="fas fa-lock" title="Secure connection (https)"></i>
<br/> <br/>
<i class="fas fa-at" title="mail: {{ form.mail.value }}"></i> <i class="fas fa-at" title="mail: {{ form.mail.value }}"></i>
@@ -67,7 +67,7 @@
<a href="{{ BASE_URL }}{{ mail_hash256 }}?s={{ size }}"> <a href="{{ BASE_URL }}{{ mail_hash256 }}?s={{ size }}">
<img src="{{ BASE_URL }}{{ mail_hash256 }}?s={{ size }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ BASE_URL }}{{ mail_hash256 }}?s={{ size }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px;"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px;">
<i class="fas fa-unlock" title="None-SSL connection (http)"></i> <i class="fas fa-unlock" title="None-SSL connection (http)"></i>
<br/> <br/>
<i class="fas fa-at" title="mail: {{ form.mail.value }}"></i> <i class="fas fa-at" title="mail: {{ form.mail.value }}"></i>
@@ -80,7 +80,7 @@
<a href="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}"> <a href="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}">
<img src="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ SECURE_BASE_URL }}{{ mail_hash256 }}?s={{ size }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px;"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px;">
<i class="fas fa-lock" title="Secure connection (https)"></i> <i class="fas fa-lock" title="Secure connection (https)"></i>
<br/> <br/>
<i class="fas fa-at" title="mail: {{ form.mail.value }}"></i> <i class="fas fa-at" title="mail: {{ form.mail.value }}"></i>
@@ -96,7 +96,7 @@
<a href="{{ openidurl }}"> <a href="{{ openidurl }}">
<img src="{{ openidurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ openidurl }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px">
<i class="fas fa-unlock" title="None-SSL connection (http)"></i> <i class="fas fa-unlock" title="None-SSL connection (http)"></i>
<br/> <br/>
<i class="fab fa-openid" title="openid: {{ form.openid.value }}"></i> <i class="fab fa-openid" title="openid: {{ form.openid.value }}"></i>
@@ -109,7 +109,7 @@
<a href="{{ openidurl_secure }}"> <a href="{{ openidurl_secure }}">
<img src="{{ openidurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;"> <img src="{{ openidurl_secure }}" style="max-width: {{ size }}px; max-height: {{ size }}px;">
</a> </a>
<div style="float:inline-end; font-size:{% widthratio size 3 1 %}px"> <div style="padding-left:2px; float:inline-end; font-size:{% widthratio size 3 1 %}px">
<i class="fas fa-lock" title="Secure connection (http)"></i> <i class="fas fa-lock" title="Secure connection (http)"></i>
<br/> <br/>
<i class="fab fa-openid" title="openid: {{ form.openid.value }}"></i> <i class="fab fa-openid" title="openid: {{ form.openid.value }}"></i>

View File

@@ -23,6 +23,7 @@
<div id="account"> <div id="account">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a href="{% url 'profile' %}">{% trans 'Profile' %}</a>&nbsp;|&nbsp; <a href="{% url 'profile' %}">{% trans 'Profile' %}</a>&nbsp;|&nbsp;
<a href="{% url 'user_preference' %}">{% trans 'Preferences' %}</a>&nbsp;|&nbsp;
<a href="{% url 'tools_check' %}">{% trans 'Check' %}</a>&nbsp;|&nbsp; <a href="{% url 'tools_check' %}">{% trans 'Check' %}</a>&nbsp;|&nbsp;
<a href="{{ contact_us }}">{% trans 'Contact Us' %}</a>&nbsp;|&nbsp; <a href="{{ contact_us }}">{% trans 'Contact Us' %}</a>&nbsp;|&nbsp;
<a href="{{ security_url }}">{% trans 'Security' %}</a>&nbsp;|&nbsp; <a href="{{ security_url }}">{% trans 'Security' %}</a>&nbsp;|&nbsp;

View File

@@ -20,6 +20,11 @@
<link rel="mask-icon" href="{% static '/img/safari-pinned-tab.svg' %}" color="#fa711f"> <link rel="mask-icon" href="{% static '/img/safari-pinned-tab.svg' %}" color="#fa711f">
<link rel="stylesheet" href="{% static '/css/ivatar.css' %}" type="text/css"> <link rel="stylesheet" href="{% static '/css/ivatar.css' %}" type="text/css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
{% if user.is_authenticated %}
{% if user.userpreference and user.userpreference.theme != 'default' %}
<link rel="stylesheet" href="{% static 'css/' %}{{ user.userpreference.theme }}.css" type="text/css">
{% endif %}
{% endif %}
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileImage" content="{% static '/img/nobody/144.png' %}"> <meta name="msapplication-TileImage" content="{% static '/img/nobody/144.png' %}">
<meta name="msapplication-TileColor" content="#fa711f"> <meta name="msapplication-TileColor" content="#fa711f">