Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • mediatek/site-interludes
  • aeltheos/site-kwei
  • mediatek/site-kwei
3 results
Show changes
Commits on Source (307)
Showing
with 1063 additions and 21 deletions
*.pyc
*~
/.vscode
__pycache__
# secret file
interludes/secret.py
myvenv
db.sqlite3
/static
.DS_Store
# for VS Code
.vscode
# Created by https://www.toptal.com/developers/gitignore/api/django
# Edit at https://www.toptal.com/developers/gitignore?templates=django
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
db.sqlite3
db.sqlite3-journal
media
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/
### Django.Python Stack ###
# Byte-compiled / optimized / DLL files
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
# Translations
*.mo
# Django stuff:
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
doc/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
pythonenv*
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# profiling data
.prof
# End of https://www.toptal.com/developers/gitignore/api/django
# Created by https://www.toptal.com/developers/gitignore/api/vscode
# Edit at https://www.toptal.com/developers/gitignore?templates=vscode
### vscode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/vscode
# Created by https://www.toptal.com/developers/gitignore/api/vim
# Edit at https://www.toptal.com/developers/gitignore?templates=vim
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# End of https://www.toptal.com/developers/gitignore/api/vim
# Created by https://www.toptal.com/developers/gitignore/api/emacs
# Edit at https://www.toptal.com/developers/gitignore?templates=emacs
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
ltximg/**
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
# End of https://www.toptal.com/developers/gitignore/api/emacs
# Created by https://www.toptal.com/developers/gitignore/api/osx
# Edit at https://www.toptal.com/developers/gitignore?templates=osx
### OSX ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.toptal.com/developers/gitignore/api/osx
# VSCodium
.vscode/
\ No newline at end of file
# Change Log
## Version 2.0.0-beta - Coming soon
- Added a form that allows admins to send emails to all users
- Added a form for users to submit activities
- Added a changeable caption for the planning
- Added fixes/improvement from 48h des jeux:
- bug fixed in activity submission form
- new validator that checks the number of slots for each activity in the planning
- fixed room display on activity page
- fixed planning info displayed on activity even when planning hidden
- added boolean field to show host email on activity
- added boolean field to separate showing slot on planning and next to activity
## Version 1.2.8 - 2021-05-06
- Added links to FAQ
- Added django-admin link to admin profile page
## Version 1.2.7 - 2021-05-03
- Fix broken discord link
## Version 1.2.6 - 2021-05-03
- Added CSS version number query string to force reload on display breaking changes
- Added more links to discord server
- Mentionned that subscriptions are optionnal
## Version 1.2.5 - 2021-04-30
- Added logo !
- Added check for inscription to multiple instances of same activity
- Fixed planning bugs
- Added missing columns to some CSV exports
- Added optionnal nullable field to change a slot's duration
## Version 1.2.4 - 2021-04-28
- Custom title to error pages
- Update FAQ
- More captions for planning
- Reworked file upload to allow for file replacement (and not just upload to a new unique name)
- Fix bugs
- Added links to home page
## Version 1.2.3 - 2021-04-25
- Planning bug fixes
## Version 1.2.2 - 2021-04-25
- Changed planning break from midnight to 4am-8am
- Added "back to top" links to activity pages
- Make slot start field non-nullable to simplify code
- Added planning caption
- Added planning PDF upload and download
## Version 1.2.1 - 2021-04-24
- Fix too small character limit on activity description
- Added links to more games
- Fix typos
- Added colors to planning
- Added caption to planning, can be set in site_settings
## Version 1.2.0 - 2021-04-07
- Update inscription form and displayed info for 'at home' event.
- Update metrics to remove unused info (meals, sleeps, ...)
- Added link to discord
- Changed models for repeated activities to a separate "slot" table (cleaner)
- Added check for slotless activity before sending mails
- Added HTML formatting for global message
## Version 1.1.0 - Repeated Activities - 2021-03-30
- Fix typos, wrong value displays
- Reworked activity display and allow HTML display inputs
- Allowed easy activity duplication
- Separate activity must_subscribe display from actual subscriptions
(so a single activity can have multiple clones: one displayed in list, and multiple in
planning and inscription lists if occurs more than once)
- Added links to activities from profile page
- Added admin warning for malformed activity wishes.
- Fixed multiple day display on planning and added planning preview to admin files
- Added version number to admin pages
## Version 1.0.1 - day one patch - 2021-03-24
- Fix missing field `is_staff` in user model
- Fix non-nullable setting `activity_submission_form`
## Version 1.0.0 - 2021-03-24
- Initial release
MIT License
Copyright (c) 2021 Dorian Lesbre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SHELL := /bin/bash
PYTHON := python3
MANAGER := manage.py
DB := db.sqlite3
SECRET := interludes/secret.py
.PHONY: help
help: ## Show this help
@echo "make: list of useful targets :"
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
.PHONY: install
install: ## Install requirements
$(PYTHON) -m pip install --upgrade pip
pip install -r requirements.txt
.PHONY: secret
secret: ## Link the secret_example.py to secret.py (only in dev mode)
secret $(SECRET):
ln -s "$(PWD)/interludes/secret_example.py" interludes/secret.py
.PHONY: migrate
migrate: $(SECRET) ## Make and run migrations
$(PYTHON) $(MANAGER) makemigrations
$(PYTHON) $(MANAGER) migrate
.PHONY: serve
serve: $(SECRET) ## Run the django server
$(PYTHON) $(MANAGER) runserver
.PHONY: host
host: $(SECRET) ## Host localy to access from same netword (make sure to add IP to ALLOWED_HOSTS)
$(PYTHON) $(MANAGER) runserver 0.0.0.0:8000
.PHONY: start
start: install $(SECRET) migrate serve ## Install requirements, apply migrations, then start development server
.PHONY: clean
clean: ## Remove migrations and delete database
find . -path "*/migrations/*.py" -not -name "__init__.py" -not -path "*/venv/*" -delete
find . -path "*/migrations/*.pyc" -not -path "*/venv/*" -delete
rm $(DB)
.PHONY: test
test: $(SECRET) ## Tests all the apps
$(PYTHON) $(MANAGER) test
.PHONY: adduser
adduser: $(SECRET) ## Create a new superuser
$(PYTHON) $(MANAGER) createsuperuser
.PHONY: shell
shell: $(SECRET) ## Run django's shell
$(PYTHON) $(MANAGER) shell
.PHONY: static
static: $(SECRET) ## collect static files
$(PYTHON) $(MANAGER) collectstatic
.PHONY: preprod
preprod: test static ## Prepare and check production
$(PYTHON) $(MANAGER) check --deploy
# Site des interludes
Ce répo contient le sites des interludes
Ce répo contient le sites des interludes. Ce site est en ligne à [https://interludes.ens.fr](https://interludes.ens.fr).
Ce répo est une copie du répo initial sur le [git interne de l'ENS Ulm](https://git.eleves.ens.fr/dlesbre/site-interludes).
Ce répo est diffusé sous une [license MIT](https://choosealicense.com/licenses/mit/).
**Contenu:**
- [Lancement rapide](#lancement-rapide)
- [Installation](#installation)
- [Lancer le serveur](#lancer-le-serveur)
- [Guide de l'administrateur](#guide-de-ladministrateur)
- [En production](#en-production)
- [Idées de développement](#idées-de-développement)
- [Liens divers](#liens-divers)
## Lancement rapide
Pour installer toutes les dépendances et lancer le serveur :
git clone https://gitlab.crans.org/mediatek/site-interludes.git &&
cd site-interlude &&
python3 -m venv venv &&
source venv/bin/activate &&
make start
Le site devrait être accessible à [http://localhost:8000](http://localhost:8000).
Par la suite vous pouver relancer le site simplement avec `make serve`.
## Installation
Pour tester modifier le repo, après l'avoir cloné :
Pour tester et modifier le repo, après l'avoir cloné :
1. Créer un environement virtuel (`python3-venv`)
1. Créer un [environement
virtuel](https://docs.python.org/3/tutorial/venv.html) (`python3-venv`)
python3 -m venv venv
(si vous le nommez autre chose que venv, ajouter le dossier correspondant au `.gitignore`)
(si vous le nommez autre chose que venv, ajouter le dossier correspondant
au `.gitignore`)
2. Lancer l'environnement virtuel
......@@ -18,24 +47,101 @@ Pour tester modifier le repo, après l'avoir cloné :
3. Installer la dernière version de pip
python -m pip install --upgrade pip
python3 -m pip install --upgrade pip
4. Installer les requirements
pip install -r requirements.txt
pip3 install -r requirements.txt
## Test
5. Copier/linker le fichier `interludes/secret_example.py` dans `interludes/secret.py`
ln -s interludes/secret_example.py interludes/secret.py
6. Faire les les migrations
make migrate
## Lancer le serveur
Pour pouvoir afficher et tester le site (après avoir tout installé)
1. Lancer l'environnement virtuel si ce n'est pas déjà fait (si le prompt du terminal ne commence pas par `(venv)`)
1. Lancer l'environnement virtuel si ce n'est pas déjà fait (si le prompt du
terminal ne commence pas par `(venv)`)
source venv/bin/activate
2. Lancer le serveur avec
2. Lancer le serveur avec `make serve` ou
make serve
Cette commande bloque le terminal, le serveur tourne tant qu'elle n'est pas
interrompue (par `Ctrl+C` ou autre).
3. Dans un navigateur, le site se trouve à l'adresse
[http://localhost:8000/](http://localhost:8000/)
4. Créer un compte super-utilisateur avec `make adduser`. Les réglages se modifient depuis les pages d'admin de Django [http://localhost:8000/admin](http://localhost:8000/admin).
## Guide de l'administrateur
Le site se gère depuis deux pages d'administration:
- celle de django [http://localhost:8000/admin](http://localhost:8000/admin) permet de modifier directement la base de donnée. Celle ci contient six tables intéressantes :
- Utilisateurs - contient tous les utilisateurs et leur permissions. Pour donner les droits d'administrateur à quelqu'un il faut lui donner le statut superutilisateur (accès à l'admin du site) ET le statut équipe (accès à l'admin django)
- Paramêtres - les réglages du site, ils permettent:
- ouvrir/fermer la création de compte, les inscriptions
- ouvrir fermer le formulaire de proposition d'activités
- afficher/cacher le planning
- renseigner l'email de contact, les dates de l'événement, les dates d'inscription
- ajouter un message global au dessus de toutes les pages
- bloquer/autoriser l'envoi d'email globaux
- Activités - liste des activités prévues. C'est ici que vous pouvez rajouter/modifier les activités qui s'affichent sur la page activité.
Un formulaire permet aux utilisateurs de proposer des activités directement. Ils vous faudra les relire et les valider ensuite manuellement pour qu'elles soient affichées sur le site.
- Crénaux - place une activité sur le planning. Une activité peut avoir plusieurs crénaux si elle a lieu plusieurs fois. Noter que les inscriptions se font à des crénaux et non a des activités.
- Participant - liste des gens inscrits et des informations sur leur inscription (ENS, repas choisi...)
- Choix d'activité - Liste de (participant, priorité, activité) indiquant les voeux des participant. Une fois que vous avez fait l'attribution, cocher les case "Obtenues" pour indiquer qui a eu quelle activité.
- celle du site [http://localhost:8000/admin_pages/](http://localhost:8000/admin_pages/)
- permet d'exporter les différentes tables au format CSV
- affiche l'état du site (version, réglages actuels, différentes métriques)
- une prévisualisation du planning
- permet d'envoyer deux séries d'emails :
- une aux inscrits pour leur communiquer les activités qu'ils ont obtenus
- une aux orgas qui ont besoin de connaître la liste des participants à l'avance pour préparer leurs activités.
- permet l'écriture d'un mail à tous.
## En production
Le serveur a besoin d'être configuré pour HTTPS et d'être configuré pour livrer directement les fichiers situés dans `/static/` et `/media/`.
1. Installer les dépendances `make install`
2. S'assurer que `DEBUG = False` et que `ALLOWED_HOSTS` contient les adresses des hôtes dans [settings.py](./interludes/settings.py)
3. Créer ou remplacer le fichier `interludes/secret.py` pour qu'il ait les mots de passe et un nouveau secret. Vous pouvez générer un secret django avec
python manage.py shell -c 'from django.core.management import utils; print(utils.get_random_secret_key())'
4. Faire les migration `make migrate`
5. Faire un `make preprod` pour générer les fichiers statiques et vérifier les réglages
## Idées de développement
A.K.A. la liste des trucs utiles que j'ai pas eu le temps d'ajouter
python manage.py runserver
- Intégrer l'[algorithme de répartition](https://github.com/Imakoala/InterludesMatchings) dans le site au lieu de le faire tourner en externe à partir des export CSV et de remplir les résultats à la main
- Envoyer une concaténation de tous les emails aux admin (pour vérification, et pas juste en copie pour éviter le spam...)
- Générer la version PDF du planning automatiquement au lieu de la faire à base de captures d'écran
- Remplacer les templates HTML statiques par du rendu de fichier markdown éditable depuis la page d'admin (afin d'éviter de devoir refaire un pull à chaque petit changement)
Cette commande bloque le terminal, le serveur tourne tant qu'elle n'est pas interrompue (par `Ctrl+C` ou autre)
## Liens divers
3. Dans un navigateur, le site se trouve à l'adresse [http://localhost:8000/](http://localhost:8000/)
- [Le site des interludes 2021](https://interludes.ens.fr)
- [Le répo initial](https://git.eleves.ens.fr/dlesbre/site-interludes) sur le gitlab de l'ENS Ulm
- [Le github de l'algorithme de répartition](https://github.com/Imakoala/InterludesMatchings)
- [Le wiki de Paris-Saclay](https://wiki.crans.org/VieBdl/InterLudes) qui recense les visuels, sites webs et photos des interludes passées.
- [Le gitlab du site des 48h des jeux](https://git.eleves.ens.fr/dlesbre/48h-des-jeux) un événement très similaire intra-ENS Ulm, c'est fork de ce répo.
- [Le site des 48h des jeux](https://48hdesjeux.cof.ens.fr/)
- [Le site du club jeu d'Ulm](https://jeux.cof.ens.fr/)
- [le site des interludes 2023](https://interludes.crans.org/)
from django.contrib import admin
from django.contrib.auth.models import Group
# Register your models here.
from accounts.models import EmailUser
from shared.admin import ExportCsvMixin
# no need for groups - we only have regular users and superusers
admin.site.unregister(Group)
@admin.register(EmailUser)
class EmailUserAdmin(ExportCsvMixin, admin.ModelAdmin):
"""option d'affichage des activités dans la vue django admin"""
filename = "export_utilisateurs.csv"
list_display = ("email", "last_name", "first_name", "is_superuser", "is_active", "email_confirmed",)
list_filter = ("is_superuser","is_active", "email_confirmed",)
fields = ("email", "last_name", "first_name", "is_superuser", "is_staff", "is_active", "email_confirmed",
("date_joined", "last_login",),
)
ordering = ("last_name", "first_name")
readonly_fields = ("date_joined", "last_login",)
list_per_page = 200
csv_export_exclude = ["password"]
......@@ -3,3 +3,4 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
verbose_name = "Comptes utilisateurs"
from django import forms
from django.contrib.auth import authenticate, password_validation
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm, PasswordResetForm
from django.utils.safestring import mark_safe
from accounts.models import EmailUser
from shared.forms import FormRenderMixin
from shared.models import normalize_email
def password_criterions_html():
"""Wraps password criterions into nice html used by other forms"""
def wrap_str(s, tagopen, tagclose=None):
if not tagclose:
tagclose = tagopen
return "<{}>{}</{}>".format(tagopen, s, tagclose)
criterions = password_validation.password_validators_help_texts()
criterions_html = wrap_str(
"\n".join(map(lambda crit: wrap_str(crit, "li"), criterions)),
'ul class="helptext"',
"ul",
)
return mark_safe(criterions_html)
class LoginForm(AuthenticationForm):
"""Form used when loging in"""
def clean(self, *args, **kwargs):
self.cleaned_data["username"] = normalize_email(self.cleaned_data.get("username"))
return super().clean(*args, **kwargs)
class CreateAccountForm(FormRenderMixin, UserCreationForm):
"""Form used to register a new user"""
class Meta:
model = EmailUser
fields = ('email', 'first_name', 'last_name', 'password1', 'password2',)
class UpdateAccountForm(FormRenderMixin, forms.ModelForm):
"""Form used to update name/email"""
class Meta:
model = EmailUser
fields = ('email', 'first_name', 'last_name')
help_texts = {"email": "Si vous la changez, il faudra confirmer la nouvelle adresse",}
def clean_email(self):
""" Check email uniqueness """
email = self.cleaned_data["email"]
if email == self.instance.email:
return email
norm_email = normalize_email(email)
if EmailUser.objects.filter(email=norm_email).count() > 0:
raise forms.ValidationError(
"Un autre compte avec cette adresse mail existe déjà."
)
return norm_email
def save(self, *args, commit=True, **kwargs):
email_changed = "email" in self.changed_data
user = super().save(*args, commit=False, **kwargs)
if email_changed:
user.email_confirmed = False
user.is_active = False
if commit:
user.save()
return user
class UpdatePasswordForm(FormRenderMixin, forms.Form):
""" Form to update one's password """
current_password = forms.CharField(
widget=forms.PasswordInput, label="Mot de passe actuel",
)
password = forms.CharField(
widget=forms.PasswordInput,
help_text=password_criterions_html(),
label="Nouveau mot de passe",
)
password_confirm = forms.CharField(
widget=forms.PasswordInput, label="Nouveau mot de passe (confirmation)",
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
def clean_current_password(self):
""" Check current password correctness """
cur_password = self.cleaned_data["current_password"]
if authenticate(username=self.user.email, password=cur_password) != self.user:
raise forms.ValidationError("Votre mot de passe actuel est incorrect.")
return cur_password
def clean_password(self):
""" Check password strength """
password = self.cleaned_data["password"]
password_validation.validate_password(password)
return password
def clean_password_confirm(self):
""" Check that both passwords match """
cleaned_data = super().clean()
password = cleaned_data.get("password")
password_confirm = cleaned_data.get("password_confirm")
if not password:
return None
if password != password_confirm:
raise forms.ValidationError(
"Les deux mots de passe ne sont pas identiques."
)
return cleaned_data
def apply(self):
""" Apply the password change, assuming validation was already passed """
self.user.set_password(self.cleaned_data["password"])
self.user.save()
class PasswordResetEmailForm(PasswordResetForm):
"""Form used when asking email to send password reset linkk"""
def clean(self, *args, **kwargs):
self.cleaned_data["email"] = normalize_email(self.cleaned_data.get("email"))
return super().clean(*args, **kwargs)
# Generated by Django 3.2.7 on 2021-10-05 18:45
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='EmailUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='adresse email')),
('first_name', models.CharField(max_length=100, verbose_name='prénom')),
('last_name', models.CharField(max_length=100, verbose_name='nom')),
('email_confirmed', models.BooleanField(default=False, verbose_name='email vérifié')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'utilisateur',
},
),
]
from django.db import models
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
# Create your models here.
from shared.models import normalize_email
class EmailUserManager(BaseUserManager):
"""User model manager that replaces username with email"""
def create_user(self, email, password, **extra_fields):
"""Create and save a User with the given email and password."""
if not email:
raise ValueError("Creating user with no email")
email = normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
"""Create and save a SuperUser with the given email and password."""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class EmailUser(AbstractUser):
"""Utilisateur identifié par son email et non
un nom d'utilisateur"""
username = None
email = models.EmailField('adresse email', unique=True)
first_name = models.CharField('prénom', max_length=100)
last_name = models.CharField("nom", max_length=100)
email_confirmed = models.BooleanField("email vérifié", default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ("last_name", "first_name",)
objects = EmailUserManager()
def __str__(self):
return self.email
class Meta:
verbose_name = "utilisateur"
{% extends "base.html" %}
{% block "content" %}
<h2>Créer un compte</h2>
<form method="post" action="{% url 'accounts:create' %}">
{% csrf_token %}
{{ form.as_html }}
<br>
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
</form>
{% endblock %}
{% autoescape off %}
Bonjour {{ user.first_name }} {{ user.last_name }},
Veuillez suivre le lien ci-dessous pour valider votre compte :
http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
--
L'équipe Interludes
{% endautoescape %}
{% autoescape off %}
Bonjour {{ user.first_name }} {{ user.last_name }},
Veuillez suivre le lien ci dessous pour valider le changement d'adresse email :
http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
--
L'équipe Interludes
{% endautoescape %}
{% autoescape off %}
Bonjour {{ user.first_name }} {{ user.last_name }},
Pour réinitialiser votre mot de passe sur le site interludes, veuillez suivre le lien suivant :
{{ protocol }}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %}
--
L'équipe Interludes
{% endautoescape %}
\ No newline at end of file
Mot de passe oublié site Interludes
\ No newline at end of file
{% extends "base.html" %}
{% block nav_login %}current{% endblock %}
{% block "content" %}
<div id="content-area">
<h2>Connexion</h2>
{% if form.errors %}
<p>Login ou mot de passe incorrect</p>
<ul class="messagelist">
<li class="error">Login ou mot de passe incorrect</li>
</ul>
{% endif %}
{% if next %}
<ul class="messagelist">
{% if user.is_authenticated %}
<p>Accès non autorisé.</p>
<li class="error">Accès non autorisé.</li>
{% else %}
<p>Merci de vous connecter.</p>
<li class="info">Vous devez vous connectez pour accéder à cette page.</li>
{% endif %}
</ul>
{% endif %}
<form method="post" action="{% url "accounts:login" %}?next={{ next|urlencode }}">
<form method="post" action="{% url 'accounts:login' %}?next={{ next|urlencode }}">
{% csrf_token %}
<table>
<tr>
......@@ -26,9 +33,15 @@
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="connexion" />
<input type="hidden" name="next" value="{{ next }}" />
<a href="{% url 'accounts:password_reset' %}">Mot de passe oublié&nbsp;?</a>
<br><br>
<div class="flex">
<input type="submit" value="Connexion">
{% if settings.registrations_open %}
<a class="button" href="{% url 'accounts:create' %}">Créer un compte</a>
{% endif %}
</div>
<input type="hidden" name="next" value="{{ next }}">
</form>
</div>
......
{% extends 'base.html' %}
{% block "content" %}
<h2>Mot de passe oublié ?</h2>
<p>Saissisez votre adresse email ci-dessous pour recevoir un lien de réinitialisation du mot de passe.</p>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
</form>
{% endblock %}
{% extends 'base.html' %}
{% block "content" %}
{% if validlink %}
<h2>Saissisez un nouveau mot de passe</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="flex">
<input type="submit" value="Changer mon mot de passe">
<a class="button" href="{% url 'accounts:login' %}">Annuler</a>
</div>
</form>
{% else %}
<h2>Lien invalide</h2>
<p>Le lien de réinitialisation est invalide, peut-être a-t-il déjà été utilisé.
Veuillez <a href="{% url 'accounts:password_reset' %}">demander un nouveau lien</a>.
</p>
{% endif %}
{% endblock %}
{% extends "base.html" %}
{% block "content" %}
<h2>Changer mes informations</h2>
<form method="post" action="{% url 'accounts:update' %}">
{% csrf_token %}
{{ update_form.as_html }}
<br>
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'profile' %}">Annuler</a>
</div>
</form>
<h2>Changer mon mot de passe</h2>
<form method="post" action="{% url 'accounts:change_password' %}">
{% csrf_token %}
{{ password_form.as_html }}
<br>
<div class="flex">
<input type="submit" value="Valider">
<a class="button" href="{% url 'profile' %}">Annuler</a>
</div>
</form>
{% endblock %}
# Code adapted from django.contrib.auth.tokens
from datetime import date
from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
from django.utils.http import base36_to_int, int_to_base36
class EmailVerificationTokenGenerator:
"""
Strategy object used to generate and check tokens for the email
verification mechanism.
"""
key_salt = "accounts.EmailVerificationTokenGenerator"
secret = settings.SECRET_KEY
def make_token(self, user):
"""
Return a token that can be used once to do a password reset
for the given user.
"""
return self._make_token_with_timestamp(user, self._num_days(self._today()))
def check_token(self, user, token):
"""
Check that a password reset token is correct for a given user.
"""
if not (user and token):
return False
# Parse the token
try:
ts_b36, _ = token.split("-")
except ValueError:
return False
try:
ts = base36_to_int(ts_b36)
except ValueError:
return False
# Check that the timestamp/uid has not been tampered with
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
return False
return True
def _make_token_with_timestamp(self, user, timestamp):
# timestamp is number of days since 2001-1-1. Converted to
# base 36, this gives us a 3 digit string until about 2121
ts_b36 = int_to_base36(timestamp)
hash_string = salted_hmac(
self.key_salt,
self._make_hash_value(user, timestamp),
secret=self.secret,
).hexdigest()[::2] # Limit to 20 characters to shorten the URL.
return "%s-%s" % (ts_b36, hash_string)
def _make_hash_value(self, user, timestamp):
"""
Hash the user's primary key and its email to make sure that the token
is invalidated after email change.
Running this data through salted_hmac() prevents cracking attempts,
provided the secret isn't compromised.
"""
# Truncate microseconds so that tokens are consistent even if the
# database doesn't support microseconds.
login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None)
return str(user.pk) + user.email + str(timestamp) + str(login_timestamp)
def _num_days(self, dt):
return (dt - date(2001, 1, 1)).days
def _today(self):
# Used for mocking in tests
return date.today()
email_token_generator = EmailVerificationTokenGenerator()