0.2.3 #99
16 changed files with 179 additions and 10 deletions
9
src/newsreader/accounts/forms.py
Normal file
9
src/newsreader/accounts/forms.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from newsreader.accounts.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettingsForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("first_name", "last_name")
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main class="main">
|
||||||
|
<form class="form password-change-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form__header">
|
||||||
|
<h1 class="h1 form__title">{% trans "Password change" %}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="fieldset password-change-form__fieldset">
|
||||||
|
{{ form }}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="fieldset password-reset-form__fieldset">
|
||||||
|
<a class="button button--cancel" href="{% url 'accounts:settings' %}">Cancel</a>
|
||||||
|
<button class="button button--confirm" type="submit">Change</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
42
src/newsreader/accounts/templates/accounts/settings.html
Normal file
42
src/newsreader/accounts/templates/accounts/settings.html
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main id="settings--page" class="main">
|
||||||
|
<form class="form settings-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form__header">
|
||||||
|
<h1 class="h1 form__title">{% trans "User settings" %}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
|
||||||
|
<section class="section form__section settings-form__section">
|
||||||
|
<fieldset class="form__fieldset settings-form__fieldset">
|
||||||
|
<label class="label settings-form__label" for="first_name">{% trans "First name" %}</label>
|
||||||
|
{{ form.first_name.errors }}
|
||||||
|
{{ form.first_name }}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="form__fieldset settings-form__fieldset">
|
||||||
|
<label class="label settings-form__label" for="last_name">{% trans "Last name" %}</label>
|
||||||
|
{{ form.last_name.errors }}
|
||||||
|
{{ form.last_name }}
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section form__section settings-form__section">
|
||||||
|
<fieldset class="form__fieldset settings-form__fieldset">
|
||||||
|
<a class="link button button--cancel" href="{% url 'index' %}">Cancel</a>
|
||||||
|
<span class="span">
|
||||||
|
<a class="link button button--primary" href="{% url 'accounts:password-change' %}">
|
||||||
|
{% trans "Change password" %}
|
||||||
|
</a>
|
||||||
|
<button class="button button--confirm">Save</button>
|
||||||
|
</span>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
29
src/newsreader/accounts/tests/test_views.py
Normal file
29
src/newsreader/accounts/tests/test_views.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from newsreader.accounts.models import User
|
||||||
|
from newsreader.accounts.tests.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettingsViewTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = UserFactory(password="test")
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
response = self.client.get(reverse("accounts:settings"))
|
||||||
|
|
||||||
|
self.assertEquals(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_user_credential_change(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("accounts:settings"),
|
||||||
|
{"first_name": "First name", "last_name": "Last name"},
|
||||||
|
)
|
||||||
|
|
||||||
|
user = User.objects.get()
|
||||||
|
|
||||||
|
self.assertRedirects(response, reverse("accounts:settings"))
|
||||||
|
|
||||||
|
self.assertEquals(user.first_name, "First name")
|
||||||
|
self.assertEquals(user.last_name, "Last name")
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from newsreader.accounts.views import (
|
from newsreader.accounts.views import (
|
||||||
|
|
@ -6,6 +7,7 @@ from newsreader.accounts.views import (
|
||||||
ActivationView,
|
ActivationView,
|
||||||
LoginView,
|
LoginView,
|
||||||
LogoutView,
|
LogoutView,
|
||||||
|
PasswordChangeView,
|
||||||
PasswordResetCompleteView,
|
PasswordResetCompleteView,
|
||||||
PasswordResetConfirmView,
|
PasswordResetConfirmView,
|
||||||
PasswordResetDoneView,
|
PasswordResetDoneView,
|
||||||
|
|
@ -13,6 +15,7 @@ from newsreader.accounts.views import (
|
||||||
RegistrationClosedView,
|
RegistrationClosedView,
|
||||||
RegistrationCompleteView,
|
RegistrationCompleteView,
|
||||||
RegistrationView,
|
RegistrationView,
|
||||||
|
SettingsView,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,5 +55,10 @@ urlpatterns = [
|
||||||
PasswordResetCompleteView.as_view(),
|
PasswordResetCompleteView.as_view(),
|
||||||
name="password-reset-complete",
|
name="password-reset-complete",
|
||||||
),
|
),
|
||||||
# TODO: create password change views
|
path(
|
||||||
|
"password-change/",
|
||||||
|
login_required(PasswordChangeView.as_view()),
|
||||||
|
name="password-change",
|
||||||
|
),
|
||||||
|
path("settings/", login_required(SettingsView.as_view()), name="settings"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@ from django.contrib.auth import views as django_views
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from django.views.generic.edit import FormView, ModelFormMixin
|
||||||
|
|
||||||
from registration.backends.default import views as registration_views
|
from registration.backends.default import views as registration_views
|
||||||
|
|
||||||
|
from newsreader.accounts.forms import UserSettingsForm
|
||||||
|
from newsreader.accounts.models import User
|
||||||
|
|
||||||
|
|
||||||
class LoginView(django_views.LoginView):
|
class LoginView(django_views.LoginView):
|
||||||
template_name = "accounts/login.html"
|
template_name = "accounts/login.html"
|
||||||
|
success_url = reverse_lazy("index")
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy("index")
|
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(django_views.LogoutView):
|
class LogoutView(django_views.LogoutView):
|
||||||
|
|
@ -89,3 +91,25 @@ class PasswordResetConfirmView(django_views.PasswordResetConfirmView):
|
||||||
|
|
||||||
class PasswordResetCompleteView(django_views.PasswordResetCompleteView):
|
class PasswordResetCompleteView(django_views.PasswordResetCompleteView):
|
||||||
template_name = "password-reset/password_reset_complete.html"
|
template_name = "password-reset/password_reset_complete.html"
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordChangeView(django_views.PasswordChangeView):
|
||||||
|
template_name = "accounts/password_change.html"
|
||||||
|
success_url = reverse_lazy("accounts:settings")
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsView(ModelFormMixin, FormView):
|
||||||
|
template_name = "accounts/settings.html"
|
||||||
|
success_url = reverse_lazy("accounts:settings")
|
||||||
|
form_class = UserSettingsForm
|
||||||
|
model = User
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_object(self, **kwargs):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
return {**super().get_form_kwargs(), "instance": self.request.user}
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ class App extends React.Component {
|
||||||
const pageHeader = (
|
const pageHeader = (
|
||||||
<>
|
<>
|
||||||
<h1 className="h1">Categories</h1>
|
<h1 className="h1">Categories</h1>
|
||||||
<a className="link button button--confirm" href="/categories/create/">
|
<a className="link button button--confirm" href="/core/categories/create/">
|
||||||
Create category
|
Create category
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main id="rules--page" class="main">
|
<main id="rules--page" class="main">
|
||||||
|
|
|
||||||
9
src/newsreader/scss/components/form/_settings-form.scss
Normal file
9
src/newsreader/scss/components/form/_settings-form.scss
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
.settings-form {
|
||||||
|
&__section:last-child {
|
||||||
|
& .settings-form__fieldset {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,3 +11,5 @@
|
||||||
|
|
||||||
@import "password-reset-form";
|
@import "password-reset-form";
|
||||||
@import "password-reset-confirm-form";
|
@import "password-reset-confirm-form";
|
||||||
|
|
||||||
|
@import "settings-form";
|
||||||
|
|
|
||||||
11
src/newsreader/scss/components/section/_text-section.scss
Normal file
11
src/newsreader/scss/components/section/_text-section.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
.text-section {
|
||||||
|
@extend .section;
|
||||||
|
|
||||||
|
width: 70%;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
@import "section";
|
@import "section";
|
||||||
|
@import "text-section";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
@mixin button-padding {
|
@mixin button-padding {
|
||||||
padding: 10px 50px;
|
padding: 7px 40px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,5 @@
|
||||||
|
|
||||||
@import "rule/index";
|
@import "rule/index";
|
||||||
@import "rules/index";
|
@import "rules/index";
|
||||||
|
|
||||||
|
@import "settings/index";
|
||||||
|
|
|
||||||
12
src/newsreader/scss/pages/settings/index.scss
Normal file
12
src/newsreader/scss/pages/settings/index.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#settings--page {
|
||||||
|
.settings-form__fieldset:last-child {
|
||||||
|
& span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
& >:first-child {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<li class="nav__item"><a href="{% url 'index' %}">Home</a></li>
|
<li class="nav__item"><a href="{% url 'index' %}">Home</a></li>
|
||||||
<li class="nav__item"><a href="{% url 'news:core:categories' %}">Categories</a></li>
|
<li class="nav__item"><a href="{% url 'news:core:categories' %}">Categories</a></li>
|
||||||
<li class="nav__item"><a href="{% url 'news:collection:rules' %}">Feeds</a></li>
|
<li class="nav__item"><a href="{% url 'news:collection:rules' %}">Feeds</a></li>
|
||||||
<li class="nav__item"><a href="#">Settings</a></li>
|
<li class="nav__item"><a href="{% url 'accounts:settings' %}">Settings</a></li>
|
||||||
{% if request.user.is_superuser %}
|
{% if request.user.is_superuser %}
|
||||||
<li class="nav__item"><a href="{% url 'admin:index' %}">Admin</a></li>
|
<li class="nav__item"><a href="{% url 'admin:index' %}">Admin</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue