Compare commits

..

17 Commits

Author SHA1 Message Date
Yohann D'ANELLO 3a52af33a2 🍻 Make coffee, closes #54 2020-08-10 15:36:41 +02:00
Alexandre Iooss ccfc1e74ac Reorder import statements of apps/activity 2020-08-10 15:30:39 +02:00
Alexandre Iooss 7719ff41ad Make tables responsive 2020-08-10 15:10:02 +02:00
Alexandre Iooss 8933fddaf3 Add comment on custom CSS 2020-08-10 15:05:00 +02:00
Alexandre Iooss eadc8fa193 Fix autocompletion not showing up in rare cases 2020-08-10 14:45:44 +02:00
Alexandre Iooss f74b19b2af Fix autocompletion popup flipping direction 2020-08-10 14:45:17 +02:00
Alexandre Iooss c3081d9cc3 Add missing space in menu 2020-08-10 13:59:29 +02:00
Alexandre Iooss 8e886f1431 Remove debug banner as it does not work 2020-08-10 13:56:06 +02:00
Alexandre Iooss bf7c253607 Set HTML lang depending on user locale 2020-08-10 13:50:23 +02:00
Alexandre Iooss 027ae5b97f Color reauth warning on login page 2020-08-10 13:49:53 +02:00
erdnaxe 63562d3fbb Merge branch 'frontnax' into 'beta'
Frontnax

See merge request bde/nk20!89
2020-08-10 12:25:26 +02:00
Alexandre Iooss bba69f0a60 Give Pikachu as an example 2020-08-10 12:09:05 +02:00
Alexandre Iooss beff848796 Use a fixed-width container by default for lisibility 2020-08-10 12:08:47 +02:00
Alexandre Iooss e78ba49252 Override Django Registration templates 2020-08-10 12:08:21 +02:00
Alexandre Iooss ce35e8f7e8 Make the container customizable 2020-08-10 11:40:51 +02:00
Alexandre Iooss 50f4a43343 Custom CSS and card for login page 2020-08-10 11:31:35 +02:00
Alexandre Iooss b66d6635fc Ignore only collected statics 2020-08-10 11:31:21 +02:00
32 changed files with 134 additions and 63 deletions

4
.gitignore vendored
View File

@ -40,8 +40,8 @@ secrets.py
map.json map.json
*.log *.log
backups/ backups/
static/ /static/
media/ /media/
# Virtualenv # Virtualenv
env/ env/

View File

@ -4,7 +4,7 @@
from django.contrib import admin from django.contrib import admin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .models import Activity, ActivityType, Guest, Entry from .models import Activity, ActivityType, Entry, Guest
@admin.register(Activity, site=admin_site) @admin.register(Activity, site=admin_site)

View File

@ -3,7 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from ..models import ActivityType, Activity, Guest, Entry, GuestTransaction from ..models import Activity, ActivityType, Entry, Guest, GuestTransaction
class ActivityTypeSerializer(serializers.ModelSerializer): class ActivityTypeSerializer(serializers.ModelSerializer):

View File

@ -1,7 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet, EntryViewSet from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
def register_activity_urls(router, path): def register_activity_urls(router, path):

View File

@ -1,12 +1,12 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from api.viewsets import ReadProtectedModelViewSet
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer, EntrySerializer from .serializers import ActivitySerializer, ActivityTypeSerializer, EntrySerializer, GuestSerializer
from ..models import ActivityType, Activity, Guest, Entry from ..models import Activity, ActivityType, Entry, Guest
class ActivityTypeViewSet(ReadProtectedModelViewSet): class ActivityTypeViewSet(ReadProtectedModelViewSet):

View File

@ -8,8 +8,8 @@ from django.contrib.contenttypes.models import ContentType
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
from note.models import NoteUser, Note from note.models import Note, NoteUser
from note_kfet.inputs import DateTimePickerInput, Autocomplete from note_kfet.inputs import Autocomplete, DateTimePickerInput
from .models import Activity, Guest from .models import Activity, Guest

View File

@ -10,8 +10,8 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from note.models import NoteUser, Transaction from note.models import NoteUser, Transaction
from rest_framework.exceptions import ValidationError
class ActivityType(models.Model): class ActivityType(models.Model):

View File

@ -7,7 +7,7 @@ import django_tables2 as tables
from django_tables2 import A from django_tables2 import A
from note.templatetags.pretty_money import pretty_money from note.templatetags.pretty_money import pretty_money
from .models import Activity, Guest, Entry from .models import Activity, Entry, Guest
class ActivityTable(tables.Table): class ActivityTable(tables.Table):

View File

@ -7,16 +7,16 @@ from django.contrib.contenttypes.models import ContentType
from django.db.models import F, Q from django.db.models import F, Q
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, TemplateView, UpdateView
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from note.models import NoteUser, Alias, NoteSpecial from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin from permission.views import ProtectQuerysetMixin
from .forms import ActivityForm, GuestForm from .forms import ActivityForm, GuestForm
from .models import Activity, Guest, Entry from .models import Activity, Entry, Guest
from .tables import ActivityTable, GuestTable, EntryTable from .tables import ActivityTable, EntryTable, GuestTable
class ActivityCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): class ActivityCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):

View File

@ -41,6 +41,10 @@ class ProfileForm(forms.ModelForm):
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"})
def save(self, commit=True): def save(self, commit=True):
if not self.instance.section or (("department" in self.changed_data if not self.instance.section or (("department" in self.changed_data
or "promotion" in self.changed_data) and "section" not in self.changed_data): or "promotion" in self.changed_data) and "section" not in self.changed_data):

View File

@ -1,5 +1,8 @@
{% extends "member/noteowner_detail.html" %} {% extends "member/noteowner_detail.html" %}
{# Use a fluid-width container #}
{% block containertype %}container-fluid{% endblock %}
{% block profile_info %} {% block profile_info %}
{% include "member/profile_info.html" %} {% include "member/profile_info.html" %}
{% endblock %} {% endblock %}

View File

@ -5,6 +5,9 @@
{# Remove page title #} {# Remove page title #}
{% block contenttitle %}{% endblock %} {% block contenttitle %}{% endblock %}
{# Use a fluid-width container #}
{% block containertype %}container-fluid{% endblock %}
{% block content %} {% block content %}
<div class="row mt-4"> <div class="row mt-4">
<div class="col-sm-5 col-md-4" id="infos_div"> <div class="col-sm-5 col-md-4" id="infos_div">

View File

@ -22,6 +22,11 @@ class SignUpForm(UserCreationForm):
self.fields['email'].required = True self.fields['email'].required = True
self.fields['email'].help_text = _("This address must be valid.") self.fields['email'].help_text = _("This address must be valid.")
# Give some example
self.fields['first_name'].widget.attrs.update({"placeholder": "Sacha"})
self.fields['last_name'].widget.attrs.update({"placeholder": "Ketchum"})
self.fields['email'].widget.attrs.update({"placeholder": "mail@example.com"})
def clean_username(self): def clean_username(self):
value = self.cleaned_data["username"] value = self.cleaned_data["username"]
if Alias.objects.filter(normalized_name=Alias.normalize(value)).exists(): if Alias.objects.filter(normalized_name=Alias.normalize(value)).exists():

View File

@ -0,0 +1,2 @@
<!-- Icon by Font Awesome, https://fontawesome.com/, Creative Common 4.0 Attribution -->
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="lock" class="svg-inline--fa fa-lock fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"></path></svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@ -0,0 +1,2 @@
<!-- Icon by Font Awesome, https://fontawesome.com/, Creative Common 4.0 Attribution -->
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user" class="svg-inline--fa fa-user fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path></svg>

After

Width:  |  Height:  |  Size: 573 B

View File

@ -0,0 +1,14 @@
/*
Add icons to login form
Font-Awesome attribution is already done inside SVG files
*/
#login-form input[type="text"] {
background: right 1rem top 50% / 5% no-repeat url('fa-user.svg');
padding-right: 3rem;
}
#login-form input[type="password"] {
background: right 1rem top 50% / 5% no-repeat url('fa-lock.svg');
padding-right: 3rem;
}

View File

@ -1,34 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-2.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block title %}{% trans "Log in" %}{% endblock %}
{% block contenttitle %}<h1>{% trans "Log in" %}</h1>{% endblock %}
{% block content %}
{% if user.is_authenticated %}
<p class="errornote">
{% blocktrans trimmed with username=request.user.username %}
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account,
or with a higher permission mask?
{% endblocktrans %}
</p>
{% endif %}
{% if request.resolver_match.view_name == 'admin:login' %}
<div class="alert alert-info">
{% blocktrans trimmed %}
You must be logged with a staff account with the higher mask to access Django Admin.
{% endblocktrans %}
</div>
{% endif %}
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
{{ form | crispy }}
<input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary">
<a href="{% url 'password_reset' %}" class="badge badge-light">{% trans 'Forgotten your password or username?' %}</a>
</form>
{% endblock %}

View File

@ -1,5 +1,8 @@
{% extends "member/noteowner_detail.html" %} {% extends "member/noteowner_detail.html" %}
{# Use a fluid-width container #}
{% block containertype %}container-fluid{% endblock %}
{% block profile_info %} {% block profile_info %}
{% include "wei/weiclub_info.html" %} {% include "wei/weiclub_info.html" %}
{% endblock %} {% endblock %}

View File

@ -27,13 +27,14 @@ ALLOWED_HOSTS = [
INSTALLED_APPS = [ INSTALLED_APPS = [
# External apps # External apps
'bootstrap_datepicker_plus',
'colorfield',
'crispy_forms',
'django_htcpcp_tea',
'django_tables2',
'mailer', 'mailer',
'phonenumber_field', 'phonenumber_field',
'polymorphic', 'polymorphic',
'crispy_forms',
'django_tables2',
'bootstrap_datepicker_plus',
'colorfield',
# Django contrib # Django contrib
'django.contrib.admin', 'django.contrib.admin',
@ -74,6 +75,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware',
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
'note_kfet.middlewares.TurbolinksMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware',
] ]

View File

@ -209,18 +209,20 @@ function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_pr
placement: 'bottom', placement: 'bottom',
title: 'Loading...', title: 'Loading...',
trigger: 'manual', trigger: 'manual',
container: field.parent() container: field.parent(),
fallbackPlacement: 'clockwise'
}); });
let old_pattern = null;
// Clear search on click // Clear search on click
field.click(function () { field.click(function () {
field.tooltip('hide'); field.tooltip('hide');
field.removeClass('is-invalid'); field.removeClass('is-invalid');
field.val(""); field.val("");
old_pattern = "";
}); });
let old_pattern = null;
// When the user type "Enter", the first alias is clicked // When the user type "Enter", the first alias is clicked
field.keypress(function (event) { field.keypress(function (event) {
if (event.originalEvent.charCode === 13 && notes.length > 0) { if (event.originalEvent.charCode === 13 && notes.length > 0) {

View File

@ -3,7 +3,7 @@
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %} {% endcomment %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="position-relative h-100"> <html lang="{{ LANGUAGE_CODE|default:"en" }}" class="position-relative h-100">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@ -22,6 +22,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}"> <meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
{# Disable turbolink cache for some pages #}
{% if no_cache %} {% if no_cache %}
<meta name="turbolinks-cache-control" content="no-cache"> <meta name="turbolinks-cache-control" content="no-cache">
{% endif %} {% endif %}
@ -72,12 +74,24 @@ SPDX-License-Identifier: GPL-3.0-or-later
.bs-tooltip-bottom .arrow::before { .bs-tooltip-bottom .arrow::before {
border-bottom-color: rgba(0,0,0,.250); border-bottom-color: rgba(0,0,0,.250);
} }
/* Limit fluid container to a max size */
.container-fluid {
max-width: 1600px;
}
/* Apply Bootstrap table-responsive to all Django tables */
.table-container {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
</style> </style>
{% block extracss %}{% endblock %} {% block extracss %}{% endblock %}
</head> </head>
<body class="d-flex w-100 h-100 flex-column"> <body class="d-flex w-100 h-100 flex-column">
{% if debug %}<div style="background:#ffeeba;text-align:center">Mode DEBUG activé.</div>{% endif %}
<main class="mb-auto"> <main class="mb-auto">
<nav class="navbar navbar-expand-md navbar-light bg-light fixed-navbar shadow-sm"> <nav class="navbar navbar-expand-md navbar-light bg-light fixed-navbar shadow-sm">
<a class="navbar-brand" href="/">{{ request.site.name }}</a> <a class="navbar-brand" href="/">{{ request.site.name }}</a>
@ -96,7 +110,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
{% if "note.transaction"|not_empty_model_list %} {% if "note.transaction"|not_empty_model_list %}
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fas fa-exchange-alt"></i>{% trans 'Transfer' %} </a> <a class="nav-link" href="{% url 'note:transfer' %}"><i class="fas fa-exchange-alt"></i> {% trans 'Transfer' %} </a>
</li> </li>
{% endif %} {% endif %}
{% if "auth.user"|model_list_length >= 2 %} {% if "auth.user"|model_list_length >= 2 %}
@ -167,7 +181,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</ul> </ul>
</div> </div>
</nav> </nav>
<div class="container-fluid my-3" style="max-width: 1600px;"> <div class="{% block containertype %}container{% endblock %} my-3">
{% if request.user.is_authenticated and not request.user.profile.email_confirmed %} {% if request.user.is_authenticated and not request.user.profile.email_confirmed %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %} {% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %}

View File

@ -0,0 +1,46 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-2.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags static %}
{# Change page title without displaying it in header #}
{% block title %}{% trans "Log in" %}{% endblock %}
{% block contenttitle %}{% endblock %}
{% block extracss %}
<link rel="stylesheet" href="{% static "registration/login.css" %}">
{% endblock %}
{% block content %}
<main class="card border-dark mx-auto" style="max-width: 30rem;">
<h3 class="card-header text-center">
{% trans "Log in" %}
</h3>
<div class="card-body">
{% if user.is_authenticated %}
<div class="alert alert-warning">
{% blocktrans trimmed with username=request.user.username %}
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account,
or with a higher permission mask?
{% endblocktrans %}
</div>
{% endif %}
{% if request.resolver_match.view_name == 'admin:login' %}
<div class="alert alert-info">
{% blocktrans trimmed %}
You must be logged with a staff account with the higher mask to access Django Admin.
{% endblocktrans %}
</div>
{% endif %}
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
{{ form | crispy }}
<input type="submit" value="{% trans 'Log in' %}" class="btn btn-primary btn-block btn-lg">
<a href="{% url 'password_reset' %}" class="badge badge-light">{% trans 'Forgotten your password or username?' %}</a>
</form>
</div>
</main>
{% endblock %}

View File

@ -4,9 +4,10 @@
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Sign up" %}{% endblock %} {% block title %}{% trans "Sign up" %}{% endblock %}
{% block content %} {# Use a fixed-width container #}
<h2>{% trans "Sign up" %}</h2> {% block containertype %}container{% endblock %}
{% block content %}
<div class="alert alert-warning"> <div class="alert alert-warning">
{% blocktrans %}If you already signed up, your registration is taken into account. The BDE must validate your account before your can log in. You have to go to the Kfet and pay the registration fee. You must also validate your email address by following the link you received.{% endblocktrans %} {% blocktrans %}If you already signed up, your registration is taken into account. The BDE must validate your account before your can log in. You have to go to the Kfet and pay the registration fee. You must also validate your email address by following the link you received.{% endblocktrans %}
</div> </div>

View File

@ -31,6 +31,9 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')), path('api/', include('api.urls')),
path('permission/', include('permission.urls')), path('permission/', include('permission.urls')),
# Make coffee
path('coffee/', include('django_htcpcp_tea.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -7,6 +7,7 @@ django-allauth==0.39.1
django-crispy-forms==1.7.2 django-crispy-forms==1.7.2
django-extensions==2.1.9 django-extensions==2.1.9
django-filter==2.2.0 django-filter==2.2.0
django-htcpcp-tea==0.3.1
django-mailer==2.0.1 django-mailer==2.0.1
django-phonenumber-field==4.0.0 django-phonenumber-field==4.0.0
django-polymorphic==2.0.3 django-polymorphic==2.0.3