-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added thematic toggle and preferences page #51
base: main
Are you sure you want to change the base?
Changes from all commits
251c3a5
bda516b
56baa4c
784d1ca
7327257
339e71c
69bd847
56d1491
fc2c5b8
3b8d801
8a90eb2
50d55af
c9ef6a2
3ad8573
f4140e9
003d993
66c2035
122e4fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Generated by Django 4.2.7 on 2023-11-29 02:51 | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("preferences", "__first__"), | ||
("accounts", "0002_alter_user_table"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="user", | ||
name="preferences", | ||
field=models.OneToOneField( | ||
blank=True, | ||
null=True, | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="preferences.preferences", | ||
), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,7 @@ | |
"homes", | ||
"residents", | ||
"work", | ||
"preferences" | ||
] | ||
|
||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||
from django import forms | ||||||
from django.forms import ModelForm | ||||||
from preferences.models import Preferences | ||||||
|
||||||
class PreferencesForm (ModelForm): | ||||||
class Meta: | ||||||
model = Preferences | ||||||
fields = ('Language', 'Mode') | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These fields should match the model fields
Suggested change
|
||||||
widgets = { | ||||||
'Language': forms.Select(attrs={'class': 'form-control'}), | ||||||
'Mode': forms.Select(attrs={'class': 'form-control'}), | ||||||
} | ||||||
labels = {'Language': 'Preferred Language', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember i18n tag for these labels using gettext. E.g. |
||||||
'Mode': 'Preferred Mode'} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from django.db import models | ||
|
||
from django.utils.translation import gettext_lazy as _ | ||
from django.conf import settings | ||
|
||
# for user preferences | ||
class Preferences (models.Model): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe adding a ‘user = models.OneToOne(…)’ field on the ‘Preferences’ model will make the view.py code simpler. |
||
class LanguageTextChoices(TextChoices): | ||
ENGLISH = "english", _("English") | ||
SUOMI = "suomi", _("Suomi") | ||
|
||
class ColorModeTextChoices(TextChoices): | ||
DARK = "dark", _("Dark") | ||
LIGHT = "light", _("Light") | ||
|
||
language = models.CharField (max_length = 30, blank=True, choices=LanguageTextChoices) | ||
color_mode = models.CharField (max_length = 30, blank=True, choices=ColorModeTextChoices) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{% extends 'base.html' %} | ||
|
||
{% load i18n %} | ||
{% load crispy_forms_tags %} | ||
|
||
{% block content %} | ||
<div class="card-body py-5 px-md-5"> | ||
<div class="row d-flex justify-content-center "> | ||
<div class="col-lg-8"> | ||
<h2 class="fw-bold " id = "set-preference-message">Set Preferences</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember i18n ´translate'tag here. |
||
<div class="container"> | ||
<div class="col-sm-20 " style="text-align: left"> | ||
<form action = "{% url 'preferences-view' %}" id = "preferences_form" method="post"> | ||
<table> | ||
{{form|crispy}} | ||
</table> | ||
{% csrf_token %} | ||
<button class="btn btn-primary " id = "submit_preferences_button" type="submit">Submit</button> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I18n tag here |
||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<div class="card-body py-5 px-md-5"> | ||
<div class="row d-flex justify-content-center "> | ||
<div class="col-lg-8"> | ||
<h2 class="fw-bold" id = "current-preferences">Your Current Preferences</h2> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember to add template internationalization so we can translate this string into the user language. |
||
<div class="col-lg-15"> | ||
{% if not request.user.preferences %} | ||
<h3 class="fw" id = "no-preferences">No current preferences. Set some!</h3> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add i18n tag ´translate' here. |
||
{% else %} | ||
{% for name, value in fields %} | ||
{% if name != "id" %} | ||
<h3 class="fw">{{ name }} : {{ value }}</h3> | ||
{% endif %} | ||
{% endfor %} | ||
{% endif %} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
{% endblock content %} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.urls import path | ||
from .views import setPreferences | ||
|
||
urlpatterns = [ | ||
path( | ||
"", | ||
setPreferences, | ||
name="preferences-view", | ||
), | ||
] |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,43 @@ | ||||||
from django.shortcuts import render, redirect, reverse | ||||||
from django.contrib.auth.decorators import login_required | ||||||
from preferences.forms import PreferencesForm | ||||||
from preferences.models import Preferences | ||||||
from django.core import serializers | ||||||
|
||||||
# this function creates a blank preference model form on GET request | ||||||
# and returns a context with the form and the fields from the preference | ||||||
# object tagged to the current user | ||||||
# on non GET request we update the database to save the new preferences | ||||||
@login_required | ||||||
def setPreferences (request): | ||||||
context = {} | ||||||
if request.method == 'GET': | ||||||
context['form'] = PreferencesForm () | ||||||
# null check for preferences field | ||||||
if request.user.preferences: | ||||||
fields = [(field.name, field.value_to_string(request.user.preferences)) for field in Preferences._meta.fields] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably unnecessary
Suggested change
|
||||||
context['fields'] = fields | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we only need to return a form instance pre-populated with the existing user preferences.Django takes care of everything else.
Suggested change
|
||||||
return render(request, '../templates/preferences.html', context) | ||||||
|
||||||
# for any other request type that is not GET | ||||||
form = PreferencesForm(request.POST) | ||||||
context['form'] = form | ||||||
|
||||||
if not form.is_valid(): | ||||||
return render(request, '../templates/preferences.html', context) | ||||||
|
||||||
preferences = Preferences( | ||||||
Language = form.cleaned_data['Language'], | ||||||
Mode = form.cleaned_data['Mode'], | ||||||
) | ||||||
|
||||||
preferences.save () | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can just be ‘form.save()’, so there is no need to create a temporary Preferences instance above
Suggested change
|
||||||
|
||||||
request.user.preferences = preferences | ||||||
request.user.save () | ||||||
|
||||||
# extract the fields to be displayed from the preferences | ||||||
fields = [(field.name, field.value_to_string(request.user.preferences)) for field in Preferences._meta.fields] | ||||||
context['fields'] = fields | ||||||
|
||||||
return render(request, '../templates/preferences.html', context) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
<!-- extra CSS to load after primary CSS --> | ||
{% block extra_css %}{% endblock extra_css %} | ||
</head> | ||
<body> | ||
<body data-bs-theme="{{ request.user.preferences.Mode }}"> | ||
{% include "navigation.html" %} | ||
|
||
<div class="container"> | ||
|
@@ -24,3 +24,4 @@ | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> | ||
</body> | ||
</html> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
|
||
/* | ||
To run these tests, do the following, add testToggle() to the eventListeners | ||
for the dropdown so that this function runs every time a user uses the toggle. | ||
If the console raises errors, then the tests do not pass. | ||
*/ | ||
|
||
function testToggle() { | ||
const currTheme = document.documentElement.getAttribute('data-bs-theme'); | ||
|
||
// SCENARIO 1: dark mode should change to light mode | ||
if (currTheme === 'dark') { | ||
// check if data-bs-theme attribute actually changes to dark mode | ||
toggleTheme('light'); | ||
assert(document.documentElement.getAttribute('data-bs-theme') === 'light'); | ||
|
||
// check if the text label correctly switches | ||
assert(document.getElementById('theme-label').innerHTML === 'Light') | ||
|
||
// check if the correct option is highlighted in the dropdown | ||
changeActiveStatus(lightMode, darkMode); | ||
assert('active' in document.getElementById('light-dropdown').getAttribute('class')) | ||
assert(!('active' in document.getElementById('dark-dropdown').getAttribute('class'))) | ||
} | ||
|
||
// SCENARIO 2: dark mode should remain if "dark" is selected | ||
if (currTheme === 'dark') { | ||
toggleTheme('dark'); | ||
assert(document.documentElement.getAttribute('data-bs-theme') === 'dark'); | ||
|
||
assert(document.getElementById('theme-label').innerHTML === 'Dark') | ||
|
||
changeActiveStatus(lightMode, darkMode); | ||
assert(!('active' in document.getElementById('light-dropdown').getAttribute('class'))) | ||
assert('active' in document.getElementById('dark-dropdown').getAttribute('class')) | ||
} | ||
|
||
// SCENARIO 3: light mode should change to dark mode | ||
if (currTheme === 'light') { | ||
toggleTheme('dark'); | ||
assert(document.documentElement.getAttribute('data-bs-theme') === 'dark'); | ||
|
||
assert(document.getElementById('theme-label').innerHTML === 'Dark') | ||
|
||
changeActiveStatus(darkMode, lightMode); | ||
assert('active' in document.getElementById('dark-dropdown').getAttribute('class')) | ||
assert(!('active' in document.getElementById('light-dropdown').getAttribute('class'))) | ||
} | ||
|
||
// SCENARIO 4: light mode should remain if "light" is selected | ||
if (currTheme === 'light') { | ||
toggleTheme('light'); | ||
assert(document.documentElement.getAttribute('data-bs-theme') === 'light'); | ||
|
||
assert(document.getElementById('theme-label').innerHTML === 'Light') | ||
|
||
changeActiveStatus(lightMode, lightMode); | ||
assert('active' in document.getElementById('light-dropdown').getAttribute('class')) | ||
assert(!('active' in document.getElementById('dark-dropdown').getAttribute('class'))) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s define the recon the Preferences model rather than the user. Then, use a ‘reverse_name=“preferences”’ on the ‘user’ OneToOne field attached to the Preferences model. That way, we assign the user to the Preferences instance directly in view.py.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.