Run pre-commit

This commit is contained in:
Gabriel Augendre 2021-07-10 12:11:58 +02:00
parent 64c58260d9
commit 18325aa59e
59 changed files with 1227 additions and 747 deletions

View File

@ -19,4 +19,3 @@ git push heroku master
You may need to upgrade Python since Heroku tends to deprecate old patch versions rather quickly. You may need to upgrade Python since Heroku tends to deprecate old patch versions rather quickly.
In this case, edit `runtime.txt`. In this case, edit `runtime.txt`.

View File

@ -7,4 +7,4 @@ SERVER_EMAIL=server@example.com
AUTHORIZED_EMAILS=user1@example.com,user2@example.com AUTHORIZED_EMAILS=user1@example.com,user2@example.com
LIBRARIAN_EMAILS=user3@example.com,user4@example.com LIBRARIAN_EMAILS=user3@example.com,user4@example.com
MAILGUN_ACCESS_KEY=access-key MAILGUN_ACCESS_KEY=access-key
MAILGUN_SERVER_NAME=mg.example.com MAILGUN_SERVER_NAME=mg.example.com

View File

@ -5,11 +5,11 @@ import requests
def main(): def main():
port = os.getenv('PORT', 8000) port = os.getenv("PORT", 8000)
res = requests.get(f'http://127.0.0.1:{port}/') res = requests.get(f"http://127.0.0.1:{port}/")
if res.status_code >= 400: if res.status_code >= 400:
sys.exit(1) sys.exit(1)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -1,57 +1,78 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from django.db.models import Prefetch from django.db.models import Prefetch
from import_export import resources, fields from import_export import fields, resources
from import_export.admin import ExportMixin from import_export.admin import ExportMixin
from import_export.widgets import IntegerWidget, DecimalWidget from import_export.widgets import DecimalWidget, IntegerWidget
from manuels.models import Teacher, Book, Level, Editor, SuppliesRequirement, CommonSupply, ISBNError from manuels.models import (
Book,
CommonSupply,
Editor,
ISBNError,
Level,
SuppliesRequirement,
Teacher,
)
class TeacherResource(resources.ModelResource): class TeacherResource(resources.ModelResource):
class Meta: class Meta:
model = Teacher model = Teacher
fields = ('first_name', 'last_name', 'email', 'phone_number') fields = ("first_name", "last_name", "email", "phone_number")
@admin.register(Teacher) @admin.register(Teacher)
class TeacherAdmin(ExportMixin, admin.ModelAdmin): class TeacherAdmin(ExportMixin, admin.ModelAdmin):
resource_class = TeacherResource resource_class = TeacherResource
list_display = ['full_name', 'email', 'phone_number', 'has_confirmed_list'] list_display = ["full_name", "email", "phone_number", "has_confirmed_list"]
def send_link(self, request, queryset): def send_link(self, request, queryset):
for teacher in queryset: for teacher in queryset:
teacher.send_link(request) teacher.send_link(request)
messages.success(request, f'Le lien a bien été envoyé aux {queryset.count()} coordonateur(s) sélectionné(s).') messages.success(
request,
f"Le lien a bien été envoyé aux {queryset.count()} coordonateur(s) sélectionné(s).",
)
send_link.short_description = 'Envoyer le lien' send_link.short_description = "Envoyer le lien"
actions = [send_link] actions = [send_link]
class LevelResource(resources.ModelResource): class LevelResource(resources.ModelResource):
non_acquired_book_count = fields.Field(attribute='non_acquired_book_count', widget=IntegerWidget()) non_acquired_book_count = fields.Field(
non_acquired_book_price = fields.Field(attribute='non_acquired_book_price', widget=DecimalWidget()) attribute="non_acquired_book_count", widget=IntegerWidget()
non_acquired_consumable_count = fields.Field(attribute='non_acquired_consumable_count', widget=IntegerWidget()) )
non_acquired_consumable_price = fields.Field(attribute='non_acquired_consumable_price', widget=DecimalWidget()) non_acquired_book_price = fields.Field(
non_acquired_total_price = fields.Field(attribute='non_acquired_total_price', widget=DecimalWidget()) attribute="non_acquired_book_price", widget=DecimalWidget()
)
non_acquired_consumable_count = fields.Field(
attribute="non_acquired_consumable_count", widget=IntegerWidget()
)
non_acquired_consumable_price = fields.Field(
attribute="non_acquired_consumable_price", widget=DecimalWidget()
)
non_acquired_total_price = fields.Field(
attribute="non_acquired_total_price", widget=DecimalWidget()
)
class Meta: class Meta:
model = Level model = Level
fields = ( fields = (
'name', "name",
'non_acquired_book_count', "non_acquired_book_count",
'non_acquired_book_price', "non_acquired_book_price",
'non_acquired_consumable_count', "non_acquired_consumable_count",
'non_acquired_consumable_price', "non_acquired_consumable_price",
'non_acquired_total_price', "non_acquired_total_price",
) )
export_order = ( export_order = (
'name', "name",
'non_acquired_book_count', "non_acquired_book_count",
'non_acquired_book_price', "non_acquired_book_price",
'non_acquired_consumable_count', "non_acquired_consumable_count",
'non_acquired_consumable_price', "non_acquired_consumable_price",
'non_acquired_total_price', "non_acquired_total_price",
) )
@ -59,87 +80,160 @@ class LevelResource(resources.ModelResource):
class LevelAdmin(ExportMixin, admin.ModelAdmin): class LevelAdmin(ExportMixin, admin.ModelAdmin):
resource_class = LevelResource resource_class = LevelResource
list_display = [ list_display = [
'name', "name",
'order', "order",
'non_acquired_book_count', "non_acquired_book_count",
'non_acquired_book_price', "non_acquired_book_price",
'non_acquired_consumable_count', "non_acquired_consumable_count",
'non_acquired_consumable_price', "non_acquired_consumable_price",
'non_acquired_total_price', "non_acquired_total_price",
] ]
list_editable = ['order'] list_editable = ["order"]
list_display_links = ['name'] list_display_links = ["name"]
def get_queryset(self, request): def get_queryset(self, request):
return super(LevelAdmin, self).get_queryset(request).prefetch_related( return (
Prefetch("book_set", to_attr="prefetched_books")) super(LevelAdmin, self)
.get_queryset(request)
.prefetch_related(Prefetch("book_set", to_attr="prefetched_books"))
)
def non_acquired_book_count(self, obj: Level): def non_acquired_book_count(self, obj: Level):
return obj.non_acquired_book_count return obj.non_acquired_book_count
non_acquired_book_count.short_description = 'Nombre de livres à acheter (hors consommables)' non_acquired_book_count.short_description = (
"Nombre de livres à acheter (hors consommables)"
)
def non_acquired_book_price(self, obj: Level): def non_acquired_book_price(self, obj: Level):
return f'{obj.non_acquired_book_price:.2f}' return f"{obj.non_acquired_book_price:.2f}"
non_acquired_book_price.short_description = 'Coût des livres à acheter (hors consommables)' non_acquired_book_price.short_description = (
"Coût des livres à acheter (hors consommables)"
)
def non_acquired_consumable_count(self, obj: Level): def non_acquired_consumable_count(self, obj: Level):
return obj.non_acquired_consumable_count return obj.non_acquired_consumable_count
non_acquired_consumable_count.short_description = 'Nombre de consommables à acheter' non_acquired_consumable_count.short_description = "Nombre de consommables à acheter"
def non_acquired_consumable_price(self, obj: Level): def non_acquired_consumable_price(self, obj: Level):
return f'{obj.non_acquired_consumable_price:.2f}' return f"{obj.non_acquired_consumable_price:.2f}"
non_acquired_consumable_price.short_description = 'Coût des consommables à acheter' non_acquired_consumable_price.short_description = "Coût des consommables à acheter"
def non_acquired_total_price(self, obj: Level): def non_acquired_total_price(self, obj: Level):
return f'{obj.non_acquired_total_price:.2f}' return f"{obj.non_acquired_total_price:.2f}"
non_acquired_total_price.short_description = 'Coût total à acheter' non_acquired_total_price.short_description = "Coût total à acheter"
class BookResource(resources.ModelResource): class BookResource(resources.ModelResource):
decitre_url = fields.Field(attribute='decitre_url') decitre_url = fields.Field(attribute="decitre_url")
class Meta: class Meta:
model = Book model = Book
fields = ('title', 'authors', 'editor__name', 'publication_year', 'isbn', 'comments', 'other_editor', fields = (
'price', 'previously_acquired', 'teacher__first_name', 'teacher__last_name', 'level__name', 'field', "title",
'consumable', "decitre_url") "authors",
export_order = ('level__name', 'field', 'title', 'authors', 'editor__name', 'publication_year', 'isbn', 'price', "editor__name",
'other_editor', 'previously_acquired', 'teacher__first_name', 'teacher__last_name', 'comments', "publication_year",
'consumable', "decitre_url") "isbn",
"comments",
"other_editor",
"price",
"previously_acquired",
"teacher__first_name",
"teacher__last_name",
"level__name",
"field",
"consumable",
"decitre_url",
)
export_order = (
"level__name",
"field",
"title",
"authors",
"editor__name",
"publication_year",
"isbn",
"price",
"other_editor",
"previously_acquired",
"teacher__first_name",
"teacher__last_name",
"comments",
"consumable",
"decitre_url",
)
@admin.register(Book) @admin.register(Book)
class BookAdmin(ExportMixin, admin.ModelAdmin): class BookAdmin(ExportMixin, admin.ModelAdmin):
resource_class = BookResource resource_class = BookResource
list_display = ['level', 'field', 'title', 'authors', 'editor', 'other_editor', 'publication_year', 'isbn', list_display = [
'price', 'previously_acquired', 'teacher', 'done', 'consumable'] "level",
list_editable = ['done'] "field",
list_filter = ['done', 'previously_acquired', 'consumable', 'level', 'editor', 'teacher'] "title",
list_display_links = ['title'] "authors",
fieldsets = [ "editor",
('Infos livre', { "other_editor",
'fields': ('title', 'consumable', 'authors', ('editor', 'other_editor'), 'publication_year', "publication_year",
'isbn', 'created_at', 'updated_at', 'comments') "isbn",
}), "price",
('Élève', { "previously_acquired",
'fields': ('price', 'previously_acquired',) "teacher",
}), "done",
('Coordonnateur', { "consumable",
'fields': ('teacher', 'level', 'field')
}),
('Gestion', {
'fields': ('done',)
}),
] ]
readonly_fields = ['created_at', 'updated_at'] list_editable = ["done"]
list_filter = [
"done",
"previously_acquired",
"consumable",
"level",
"editor",
"teacher",
]
list_display_links = ["title"]
fieldsets = [
(
"Infos livre",
{
"fields": (
"title",
"consumable",
"authors",
("editor", "other_editor"),
"publication_year",
"isbn",
"created_at",
"updated_at",
"comments",
)
},
),
(
"Élève",
{
"fields": (
"price",
"previously_acquired",
)
},
),
("Coordonnateur", {"fields": ("teacher", "level", "field")}),
("Gestion", {"fields": ("done",)}),
]
readonly_fields = ["created_at", "updated_at"]
def get_queryset(self, request): def get_queryset(self, request):
return super(BookAdmin, self).get_queryset(request).select_related("editor", "level", "teacher") return (
super(BookAdmin, self)
.get_queryset(request)
.select_related("editor", "level", "teacher")
)
def update_with_decitre(self, request, queryset): def update_with_decitre(self, request, queryset):
for book in queryset: for book in queryset:
@ -147,15 +241,15 @@ class BookAdmin(ExportMixin, admin.ModelAdmin):
book.update_from_decitre() book.update_from_decitre()
messages.success( messages.success(
request, request,
f'Mise à jour réussie du livre "{book.title}" ({book.level.name} - {book.field})' f'Mise à jour réussie du livre "{book.title}" ({book.level.name} - {book.field})',
) )
except ISBNError as e: except ISBNError as e:
messages.warning( messages.warning(
request, request,
f'Erreur lors de la mise à jour du livre "{book.title}" ({book.level.name} - {book.field}) : {e.data.get("error")}' f'Erreur lors de la mise à jour du livre "{book.title}" ({book.level.name} - {book.field}) : {e.data.get("error")}',
) )
update_with_decitre.short_description = 'Mettre à jour avec Decitre' update_with_decitre.short_description = "Mettre à jour avec Decitre"
def mark_as_done(self, request, queryset): def mark_as_done(self, request, queryset):
queryset.update(done=True) queryset.update(done=True)
@ -171,25 +265,41 @@ class EditorAdmin(admin.ModelAdmin):
@admin.register(CommonSupply) @admin.register(CommonSupply)
class CommonSupplyAdmin(admin.ModelAdmin): class CommonSupplyAdmin(admin.ModelAdmin):
list_display = ['name', 'order'] list_display = ["name", "order"]
list_display_links = ['name'] list_display_links = ["name"]
list_editable = ['order'] list_editable = ["order"]
class SuppliesResource(resources.ModelResource): class SuppliesResource(resources.ModelResource):
class Meta: class Meta:
model = SuppliesRequirement model = SuppliesRequirement
fields = ('supplies', 'field', 'level__name', 'teacher__first_name', 'teacher__last_name') fields = (
export_order = ('level__name', 'field', 'supplies', 'teacher__first_name', 'teacher__last_name') "supplies",
"field",
"level__name",
"teacher__first_name",
"teacher__last_name",
)
export_order = (
"level__name",
"field",
"supplies",
"teacher__first_name",
"teacher__last_name",
)
@admin.register(SuppliesRequirement) @admin.register(SuppliesRequirement)
class SuppliesRequirementAdmin(ExportMixin, admin.ModelAdmin): class SuppliesRequirementAdmin(ExportMixin, admin.ModelAdmin):
resource_class = SuppliesResource resource_class = SuppliesResource
list_display = ['id', 'teacher', 'level', 'field', 'supplies', 'done'] list_display = ["id", "teacher", "level", "field", "supplies", "done"]
list_editable = ['done'] list_editable = ["done"]
readonly_fields = ['created_at', 'updated_at'] readonly_fields = ["created_at", "updated_at"]
list_filter = ['done', 'teacher', 'level'] list_filter = ["done", "teacher", "level"]
def get_queryset(self, request): def get_queryset(self, request):
return super(SuppliesRequirementAdmin, self).get_queryset(request).select_related("level", "teacher") return (
super(SuppliesRequirementAdmin, self)
.get_queryset(request)
.select_related("level", "teacher")
)

View File

@ -2,4 +2,4 @@ from django.apps import AppConfig
class ManuelsConfig(AppConfig): class ManuelsConfig(AppConfig):
name = 'manuels' name = "manuels"

View File

@ -2,4 +2,4 @@ from django.conf import settings
def authorized_mails(request): def authorized_mails(request):
return {'authorized_mails': settings.AUTHORIZED_EMAILS} return {"authorized_mails": settings.AUTHORIZED_EMAILS}

View File

@ -1,93 +1,136 @@
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from manuels.models import Book, SuppliesRequirement, Level from manuels.models import Book, Level, SuppliesRequirement
class EditBookForm(forms.ModelForm): class EditBookForm(forms.ModelForm):
class Meta: class Meta:
model = Book model = Book
fields = ['teacher', 'level', 'field', 'no_book', 'see_later', 'title', 'authors', 'editor', 'other_editor', fields = [
'publication_year', 'isbn', 'price', 'previously_acquired', 'comments', 'consumable'] "teacher",
"level",
"field",
"no_book",
"see_later",
"title",
"authors",
"editor",
"other_editor",
"publication_year",
"isbn",
"price",
"previously_acquired",
"comments",
"consumable",
]
no_book = forms.BooleanField(label='Pas de livre pour cette classe/matière', required=False, initial=False) no_book = forms.BooleanField(
label="Pas de livre pour cette classe/matière", required=False, initial=False
)
see_later = forms.BooleanField( see_later = forms.BooleanField(
label='Voir à la rentrée', help_text="Notamment en cas de désaccord sur l'adoption ou non d'un manuel", label="Voir à la rentrée",
required=False, initial=False help_text="Notamment en cas de désaccord sur l'adoption ou non d'un manuel",
required=False,
initial=False,
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['title'].widget = forms.TextInput() self.fields["title"].widget = forms.TextInput()
self.fields['authors'].widget = forms.TextInput() self.fields["authors"].widget = forms.TextInput()
self.fields['comments'].widget.attrs.update(rows=3) self.fields["comments"].widget.attrs.update(rows=3)
self.fields['teacher'].widget.attrs.update({'class': 'custom-select'}) self.fields["teacher"].widget.attrs.update({"class": "custom-select"})
self.fields['editor'].widget.attrs.update({'class': 'custom-select'}) self.fields["editor"].widget.attrs.update({"class": "custom-select"})
self.fields['previously_acquired'].widget.attrs.update({'class': 'custom-select'}) self.fields["previously_acquired"].widget.attrs.update(
self.fields['consumable'].widget.attrs.update({'class': 'custom-select'}) {"class": "custom-select"}
if 'level' in self.fields: )
self.fields['level'].widget.attrs.update({'class': 'custom-select'}) self.fields["consumable"].widget.attrs.update({"class": "custom-select"})
if "level" in self.fields:
self.fields["level"].widget.attrs.update({"class": "custom-select"})
def clean(self): def clean(self):
editor = self.cleaned_data['editor'] editor = self.cleaned_data["editor"]
other_editor = self.cleaned_data['other_editor'] other_editor = self.cleaned_data["other_editor"]
title = self.cleaned_data['title'] title = self.cleaned_data["title"]
if (editor if (
and 'autre' in editor.name.lower() editor
and not other_editor and "autre" in editor.name.lower()
and title not in ['PAS DE LIVRE POUR CETTE CLASSE', 'VOIR À LA RENTRÉE']): and not other_editor
and title not in ["PAS DE LIVRE POUR CETTE CLASSE", "VOIR À LA RENTRÉE"]
):
self.add_error( self.add_error(
'other_editor', "other_editor",
ValidationError( ValidationError(
"Vous devez préciser l'éditeur si vous n'en choisissez pas un parmi la liste.", "Vous devez préciser l'éditeur si vous n'en choisissez pas un parmi la liste.",
code='missing' code="missing",
) ),
) )
def clean_previously_acquired(self): def clean_previously_acquired(self):
data = self.cleaned_data['previously_acquired'] data = self.cleaned_data["previously_acquired"]
if data is None or data == '': if data is None or data == "":
raise ValidationError('Vous devez choisir une valeur') raise ValidationError("Vous devez choisir une valeur")
return data return data
class AddBookForm(EditBookForm): class AddBookForm(EditBookForm):
class Meta(EditBookForm.Meta): class Meta(EditBookForm.Meta):
fields = ['teacher', 'levels', 'field', 'no_book', 'see_later', 'title', 'authors', 'editor', 'other_editor', fields = [
'publication_year', 'isbn', 'price', 'previously_acquired', 'comments', 'add_another', 'consumable'] "teacher",
"levels",
"field",
"no_book",
"see_later",
"title",
"authors",
"editor",
"other_editor",
"publication_year",
"isbn",
"price",
"previously_acquired",
"comments",
"add_another",
"consumable",
]
add_another = forms.BooleanField(label='Ajouter un autre livre', required=False, initial=True) add_another = forms.BooleanField(
label="Ajouter un autre livre", required=False, initial=True
)
levels = forms.ModelMultipleChoiceField( levels = forms.ModelMultipleChoiceField(
queryset=Level.objects.all(), queryset=Level.objects.all(),
label='Classes', label="Classes",
required=True, required=True,
help_text='Maintenez la touche Ctrl (ou Cmd) enfoncée pour en sélectionner plusieurs.' help_text="Maintenez la touche Ctrl (ou Cmd) enfoncée pour en sélectionner plusieurs.",
) )
class EditSuppliesForm(forms.ModelForm): class EditSuppliesForm(forms.ModelForm):
class Meta: class Meta:
model = SuppliesRequirement model = SuppliesRequirement
fields = ['teacher', 'level', 'field', 'supplies'] fields = ["teacher", "level", "field", "supplies"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['supplies'].widget.attrs.update(rows=3) self.fields["supplies"].widget.attrs.update(rows=3)
self.fields['teacher'].widget.attrs.update({'class': 'custom-select'}) self.fields["teacher"].widget.attrs.update({"class": "custom-select"})
if 'level' in self.fields: if "level" in self.fields:
self.fields['level'].widget.attrs.update({'class': 'custom-select'}) self.fields["level"].widget.attrs.update({"class": "custom-select"})
class AddSuppliesForm(EditSuppliesForm): class AddSuppliesForm(EditSuppliesForm):
class Meta(EditSuppliesForm.Meta): class Meta(EditSuppliesForm.Meta):
fields = ['teacher', 'levels', 'field', 'supplies'] fields = ["teacher", "levels", "field", "supplies"]
add_another = forms.BooleanField(label="Ajouter d'autres fournitures", required=False, initial=True) add_another = forms.BooleanField(
label="Ajouter d'autres fournitures", required=False, initial=True
)
levels = forms.ModelMultipleChoiceField( levels = forms.ModelMultipleChoiceField(
queryset=Level.objects.all(), queryset=Level.objects.all(),
label='Classes', label="Classes",
required=True, required=True,
help_text='Maintenez la touche Ctrl (ou Cmd) enfoncée pour en sélectionner plusieurs.' help_text="Maintenez la touche Ctrl (ou Cmd) enfoncée pour en sélectionner plusieurs.",
) )

View File

@ -3,7 +3,7 @@ from django.core.management import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
help = 'Clears django cache' help = "Clears django cache"
def handle(self, *args, **options): def handle(self, *args, **options):
cache.clear() cache.clear()

View File

@ -1,71 +1,142 @@
# Generated by Django 2.0.5 on 2018-05-21 22:47 # Generated by Django 2.0.5 on 2018-05-21 22:47
from django.db import migrations, models
import django.db.models.deletion
import manuels.models
import uuid import uuid
import django.db.models.deletion
from django.db import migrations, models
import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Book', name="Book",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('field', models.CharField(max_length=100, verbose_name='matière')), "id",
('title', models.TextField(verbose_name='titre')), models.AutoField(
('authors', models.TextField(verbose_name='auteurs')), auto_created=True,
('editor', models.CharField(max_length=200, verbose_name='éditeur')), primary_key=True,
('collection', models.CharField(blank=True, max_length=200, verbose_name='collection')), serialize=False,
('publication_year', models.PositiveIntegerField(verbose_name='année de publication')), verbose_name="ID",
('isbn', models.CharField(max_length=20, validators=[manuels.models.isbn_validator], verbose_name='ISBN/EAN')), ),
('price', models.PositiveIntegerField(verbose_name='prix')), ),
('previously_acquired', models.BooleanField(choices=[(True, 'Oui'), (False, 'Non')], verbose_name="manuel acquis précédemment par l'élève")), ("field", models.CharField(max_length=100, verbose_name="matière")),
("title", models.TextField(verbose_name="titre")),
("authors", models.TextField(verbose_name="auteurs")),
("editor", models.CharField(max_length=200, verbose_name="éditeur")),
(
"collection",
models.CharField(
blank=True, max_length=200, verbose_name="collection"
),
),
(
"publication_year",
models.PositiveIntegerField(verbose_name="année de publication"),
),
(
"isbn",
models.CharField(
max_length=20,
validators=[manuels.models.isbn_validator],
verbose_name="ISBN/EAN",
),
),
("price", models.PositiveIntegerField(verbose_name="prix")),
(
"previously_acquired",
models.BooleanField(
choices=[(True, "Oui"), (False, "Non")],
verbose_name="manuel acquis précédemment par l'élève",
),
),
], ],
options={ options={
'verbose_name': 'livre', "verbose_name": "livre",
'verbose_name_plural': 'livres', "verbose_name_plural": "livres",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Level', name="Level",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=10, verbose_name='nom')), "id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=10, verbose_name="nom")),
], ],
options={ options={
'verbose_name': 'classe', "verbose_name": "classe",
'verbose_name_plural': 'classes', "verbose_name_plural": "classes",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Teacher', name="Teacher",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('first_name', models.CharField(max_length=100, verbose_name='prénom')), "uuid",
('last_name', models.CharField(max_length=100, verbose_name='nom')), models.UUIDField(
('phone_number', models.CharField(help_text="En cas d'urgence", max_length=10, verbose_name='numéro de téléphone')), default=uuid.uuid4,
('email', models.EmailField(help_text='Utilisée pour vous transmettre votre lien personnel', max_length=254, unique=True, verbose_name='adresse email')), editable=False,
primary_key=True,
serialize=False,
),
),
("first_name", models.CharField(max_length=100, verbose_name="prénom")),
("last_name", models.CharField(max_length=100, verbose_name="nom")),
(
"phone_number",
models.CharField(
help_text="En cas d'urgence",
max_length=10,
verbose_name="numéro de téléphone",
),
),
(
"email",
models.EmailField(
help_text="Utilisée pour vous transmettre votre lien personnel",
max_length=254,
unique=True,
verbose_name="adresse email",
),
),
], ],
options={ options={
'verbose_name': 'enseignant', "verbose_name": "enseignant",
'verbose_name_plural': 'enseignants', "verbose_name_plural": "enseignants",
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='level', name="level",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='manuels.Level', verbose_name='classe'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manuels.Level",
verbose_name="classe",
),
), ),
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='teacher', name="teacher",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='manuels.Teacher', verbose_name='enseignant'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manuels.Teacher",
verbose_name="enseignant",
),
), ),
] ]

View File

@ -1,30 +1,43 @@
# Generated by Django 2.0.5 on 2018-05-21 23:32 # Generated by Django 2.0.5 on 2018-05-21 23:32
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0001_initial'), ("manuels", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Editor', name="Editor",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=50, verbose_name='nom')), "id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50, verbose_name="nom")),
], ],
options={ options={
'verbose_name': 'éditeur', "verbose_name": "éditeur",
'verbose_name_plural': 'éditeurs', "verbose_name_plural": "éditeurs",
}, },
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='editor', name="editor",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='manuels.Editor', verbose_name='éditeur'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manuels.Editor",
verbose_name="éditeur",
),
), ),
] ]

View File

@ -1,58 +1,66 @@
# Generated by Django 2.0.5 on 2018-05-21 23:45 # Generated by Django 2.0.5 on 2018-05-21 23:45
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0002_auto_20180522_0132'), ("manuels", "0002_auto_20180522_0132"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True), field=models.DateTimeField(auto_now=True),
), ),
migrations.AddField( migrations.AddField(
model_name='editor', model_name="editor",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='editor', model_name="editor",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True), field=models.DateTimeField(auto_now=True),
), ),
migrations.AddField( migrations.AddField(
model_name='level', model_name="level",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='level', model_name="level",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True), field=models.DateTimeField(auto_now=True),
), ),
migrations.AddField( migrations.AddField(
model_name='teacher', model_name="teacher",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='teacher', model_name="teacher",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True), field=models.DateTimeField(auto_now=True),
), ),
] ]

View File

@ -6,48 +6,48 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0003_auto_20180522_0145'), ("manuels", "0003_auto_20180522_0145"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, verbose_name='créé le'), field=models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name='mis à jour le'), field=models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='editor', model_name="editor",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, verbose_name='créé le'), field=models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='editor', model_name="editor",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name='mis à jour le'), field=models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='level', model_name="level",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, verbose_name='créé le'), field=models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='level', model_name="level",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name='mis à jour le'), field=models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='teacher', model_name="teacher",
name='created_at', name="created_at",
field=models.DateTimeField(auto_now_add=True, verbose_name='créé le'), field=models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='teacher', model_name="teacher",
name='updated_at', name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name='mis à jour le'), field=models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
), ),
] ]

View File

@ -1,29 +1,59 @@
# Generated by Django 2.0.5 on 2018-05-22 07:34 # Generated by Django 2.0.5 on 2018-05-22 07:34
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0004_auto_20180522_0148'), ("manuels", "0004_auto_20180522_0148"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='SuppliesRequirement', name="SuppliesRequirement",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='créé le')), "id",
('updated_at', models.DateTimeField(auto_now=True, verbose_name='mis à jour le')), models.AutoField(
('supplies', models.TextField(verbose_name='fournitures')), auto_created=True,
('level', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='manuels.Level', verbose_name='classe')), primary_key=True,
('teacher', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='manuels.Teacher', verbose_name='enseignant')), serialize=False,
verbose_name="ID",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
),
("supplies", models.TextField(verbose_name="fournitures")),
(
"level",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manuels.Level",
verbose_name="classe",
),
),
(
"teacher",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="manuels.Teacher",
verbose_name="enseignant",
),
),
], ],
options={ options={
'verbose_name': 'demande de fournitures', "verbose_name": "demande de fournitures",
'verbose_name_plural': 'demandes de fournitures', "verbose_name_plural": "demandes de fournitures",
}, },
), ),
] ]

View File

@ -6,13 +6,17 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0005_suppliesrequirement'), ("manuels", "0005_suppliesrequirement"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='previously_acquired', name="previously_acquired",
field=models.BooleanField(choices=[(True, 'Oui'), (False, 'Non')], default=False, verbose_name="manuel acquis précédemment par l'élève"), field=models.BooleanField(
choices=[(True, "Oui"), (False, "Non")],
default=False,
verbose_name="manuel acquis précédemment par l'élève",
),
), ),
] ]

View File

@ -6,13 +6,17 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0006_auto_20180522_1009'), ("manuels", "0006_auto_20180522_1009"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='previously_acquired', name="previously_acquired",
field=models.BooleanField(choices=[(False, 'Non'), (True, 'Oui')], default=False, verbose_name="manuel acquis précédemment par l'élève"), field=models.BooleanField(
choices=[(False, "Non"), (True, "Oui")],
default=False,
verbose_name="manuel acquis précédemment par l'élève",
),
), ),
] ]

View File

@ -1,39 +1,64 @@
# Generated by Django 2.0.5 on 2018-05-22 08:51 # Generated by Django 2.0.5 on 2018-05-22 08:51
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0007_auto_20180522_1026'), ("manuels", "0007_auto_20180522_1026"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='editor', name="editor",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Editor', verbose_name='éditeur'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Editor",
verbose_name="éditeur",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='level', name="level",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Level', verbose_name='classe'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Level",
verbose_name="classe",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='teacher', name="teacher",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Teacher', verbose_name='enseignant'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Teacher",
verbose_name="enseignant",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='level', name="level",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Level', verbose_name='classe'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Level",
verbose_name="classe",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='teacher', name="teacher",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Teacher', verbose_name='enseignant'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Teacher",
verbose_name="enseignant",
),
), ),
] ]

View File

@ -6,14 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0008_auto_20180522_1051'), ("manuels", "0008_auto_20180522_1051"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='field', name="field",
field=models.CharField(default='', max_length=100, verbose_name='matière'), field=models.CharField(default="", max_length=100, verbose_name="matière"),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0009_suppliesrequirement_field'), ("manuels", "0009_suppliesrequirement_field"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='book', model_name="book",
name='collection', name="collection",
), ),
] ]

View File

@ -1,19 +1,25 @@
# Generated by Django 2.0.5 on 2018-05-23 22:13 # Generated by Django 2.0.5 on 2018-05-23 22:13
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0010_remove_book_collection'), ("manuels", "0010_remove_book_collection"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='teacher', model_name="teacher",
name='phone_number', name="phone_number",
field=models.CharField(help_text="En cas d'urgence, 10 chiffres.", max_length=10, validators=[manuels.models.phone_validator], verbose_name='numéro de téléphone'), field=models.CharField(
help_text="En cas d'urgence, 10 chiffres.",
max_length=10,
validators=[manuels.models.phone_validator],
verbose_name="numéro de téléphone",
),
), ),
] ]

View File

@ -1,19 +1,25 @@
# Generated by Django 2.0.5 on 2018-05-23 22:14 # Generated by Django 2.0.5 on 2018-05-23 22:14
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0011_auto_20180524_0013'), ("manuels", "0011_auto_20180524_0013"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='isbn', name="isbn",
field=models.CharField(help_text='Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>', max_length=20, validators=[manuels.models.isbn_validator], verbose_name='ISBN/EAN'), field=models.CharField(
help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>",
max_length=20,
validators=[manuels.models.isbn_validator],
verbose_name="ISBN/EAN",
),
), ),
] ]

View File

@ -6,28 +6,28 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0012_auto_20180524_0014'), ("manuels", "0012_auto_20180524_0014"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='field', name="field",
field=models.CharField(max_length=200, verbose_name='matière'), field=models.CharField(max_length=200, verbose_name="matière"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='editor', model_name="editor",
name='name', name="name",
field=models.CharField(max_length=100, verbose_name='nom'), field=models.CharField(max_length=100, verbose_name="nom"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='level', model_name="level",
name='name', name="name",
field=models.CharField(max_length=50, verbose_name='nom'), field=models.CharField(max_length=50, verbose_name="nom"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='field', name="field",
field=models.CharField(max_length=200, verbose_name='matière'), field=models.CharField(max_length=200, verbose_name="matière"),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0013_auto_20180524_0016'), ("manuels", "0013_auto_20180524_0016"),
] ]
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
old_name='field', old_name="field",
new_name='fields', new_name="fields",
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0014_auto_20180524_0034'), ("manuels", "0014_auto_20180524_0034"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='fields', name="fields",
field=models.TextField(verbose_name='matières'), field=models.TextField(verbose_name="matières"),
), ),
] ]

View File

@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0015_auto_20180524_0035'), ("manuels", "0015_auto_20180524_0035"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='field', name="field",
field=models.CharField(max_length=200, verbose_name='discipline'), field=models.CharField(max_length=200, verbose_name="discipline"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='fields', name="fields",
field=models.TextField(verbose_name='disciplines'), field=models.TextField(verbose_name="disciplines"),
), ),
] ]

View File

@ -6,18 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0016_auto_20180530_1801'), ("manuels", "0016_auto_20180530_1801"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='done', name="done",
field=models.BooleanField(default=False, verbose_name='Traité'), field=models.BooleanField(default=False, verbose_name="Traité"),
), ),
migrations.AddField( migrations.AddField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='done', name="done",
field=models.BooleanField(default=False, verbose_name='Traité'), field=models.BooleanField(default=False, verbose_name="Traité"),
), ),
] ]

View File

@ -6,14 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0017_auto_20180530_1804'), ("manuels", "0017_auto_20180530_1804"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='comments', name="comments",
field=models.TextField(default='', help_text="Ce message sera visible par la documentaliste. Vous pouvez l'utiliser par exemple si vous souhaitez saisir un éditeur qui n'est pas proposé.", verbose_name='commentaires'), field=models.TextField(
default="",
help_text="Ce message sera visible par la documentaliste. Vous pouvez l'utiliser par exemple si vous souhaitez saisir un éditeur qui n'est pas proposé.",
verbose_name="commentaires",
),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@ -6,13 +6,17 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0018_book_comments'), ("manuels", "0018_book_comments"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='comments', name="comments",
field=models.TextField(blank=True, help_text="Ce message sera visible par la documentaliste. Vous pouvez l'utiliser par exemple si vous souhaitez saisir un éditeur qui n'est pas proposé.", verbose_name='commentaires'), field=models.TextField(
blank=True,
help_text="Ce message sera visible par la documentaliste. Vous pouvez l'utiliser par exemple si vous souhaitez saisir un éditeur qui n'est pas proposé.",
verbose_name="commentaires",
),
), ),
] ]

View File

@ -1,28 +1,41 @@
# Generated by Django 2.0.5 on 2018-06-02 14:30 # Generated by Django 2.0.5 on 2018-06-02 14:30
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0019_auto_20180531_0815'), ("manuels", "0019_auto_20180531_0815"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='teacher', name="teacher",
options={'verbose_name': 'coordonnateur', 'verbose_name_plural': 'coordonnateurs'}, options={
"verbose_name": "coordonnateur",
"verbose_name_plural": "coordonnateurs",
},
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='teacher', name="teacher",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Teacher', verbose_name='coordonnateur'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Teacher",
verbose_name="coordonnateur",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='teacher', name="teacher",
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='manuels.Teacher', verbose_name='coordonnateur'), field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="manuels.Teacher",
verbose_name="coordonnateur",
),
), ),
] ]

View File

@ -6,16 +6,24 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0020_auto_20180602_1630'), ("manuels", "0020_auto_20180602_1630"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='editor', name="editor",
options={'ordering': ['name'], 'verbose_name': 'éditeur', 'verbose_name_plural': 'éditeurs'}, options={
"ordering": ["name"],
"verbose_name": "éditeur",
"verbose_name_plural": "éditeurs",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='teacher', name="teacher",
options={'ordering': ['first_name'], 'verbose_name': 'coordonnateur', 'verbose_name_plural': 'coordonnateurs'}, options={
"ordering": ["first_name"],
"verbose_name": "coordonnateur",
"verbose_name_plural": "coordonnateurs",
},
), ),
] ]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0021_auto_20180602_1638'), ("manuels", "0021_auto_20180602_1638"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='teacher', model_name="teacher",
name='has_confirmed_list', name="has_confirmed_list",
field=models.BooleanField(default=False, verbose_name='a confirmé les listes'), field=models.BooleanField(
default=False, verbose_name="a confirmé les listes"
),
), ),
] ]

View File

@ -1,19 +1,23 @@
# Generated by Django 2.0.5 on 2018-06-04 16:27 # Generated by Django 2.0.5 on 2018-06-04 16:27
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0022_teacher_has_confirmed_list'), ("manuels", "0022_teacher_has_confirmed_list"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='price', name="price",
field=models.FloatField(validators=[manuels.models.positive_float_validator], verbose_name='prix'), field=models.FloatField(
validators=[manuels.models.positive_float_validator],
verbose_name="prix",
),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0023_auto_20180604_1827'), ("manuels", "0023_auto_20180604_1827"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='other_editor', name="other_editor",
field=models.CharField(blank=True, max_length=100, verbose_name='préciser'), field=models.CharField(blank=True, max_length=100, verbose_name="préciser"),
), ),
] ]

View File

@ -6,13 +6,17 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0024_book_other_editor'), ("manuels", "0024_book_other_editor"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='comments', name="comments",
field=models.TextField(blank=True, help_text='Ce message sera visible par la documentaliste.', verbose_name='commentaires'), field=models.TextField(
blank=True,
help_text="Ce message sera visible par la documentaliste.",
verbose_name="commentaires",
),
), ),
] ]

View File

@ -1,19 +1,25 @@
# Generated by Django 2.0.6 on 2018-06-16 07:16 # Generated by Django 2.0.6 on 2018-06-16 07:16
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0025_auto_20180607_0746'), ("manuels", "0025_auto_20180607_0746"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='isbn', name="isbn",
field=models.CharField(help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à13 chiffres (ou EAN)", max_length=20, validators=[manuels.models.isbn_validator], verbose_name='ISBN/EAN'), field=models.CharField(
help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à13 chiffres (ou EAN)",
max_length=20,
validators=[manuels.models.isbn_validator],
verbose_name="ISBN/EAN",
),
), ),
] ]

View File

@ -6,28 +6,34 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0026_auto_20180616_0916'), ("manuels", "0026_auto_20180616_0916"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='done', name="done",
field=models.BooleanField(blank=True, default=False, verbose_name='Traité'), field=models.BooleanField(blank=True, default=False, verbose_name="Traité"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='previously_acquired', name="previously_acquired",
field=models.BooleanField(choices=[(None, '------------'), (False, 'Non'), (True, 'Oui')], default=None, verbose_name="manuel acquis précédemment par l'élève"), field=models.BooleanField(
choices=[(None, "------------"), (False, "Non"), (True, "Oui")],
default=None,
verbose_name="manuel acquis précédemment par l'élève",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='done', name="done",
field=models.BooleanField(blank=True, default=False, verbose_name='Traité'), field=models.BooleanField(blank=True, default=False, verbose_name="Traité"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='teacher', model_name="teacher",
name='has_confirmed_list', name="has_confirmed_list",
field=models.BooleanField(blank=True, default=False, verbose_name='a confirmé les listes'), field=models.BooleanField(
blank=True, default=False, verbose_name="a confirmé les listes"
),
), ),
] ]

View File

@ -6,23 +6,31 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0027_auto_20190401_2036'), ("manuels", "0027_auto_20190401_2036"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='level', name="level",
options={'ordering': ['order', 'name'], 'verbose_name': 'classe', 'verbose_name_plural': 'classes'}, options={
"ordering": ["order", "name"],
"verbose_name": "classe",
"verbose_name_plural": "classes",
},
), ),
migrations.AddField( migrations.AddField(
model_name='book', model_name="book",
name='consumable', name="consumable",
field=models.BooleanField(default=False, help_text="Exemple : un cahier d'exercices est un consommable", verbose_name='consommable'), field=models.BooleanField(
default=False,
help_text="Exemple : un cahier d'exercices est un consommable",
verbose_name="consommable",
),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='level', model_name="level",
name='order', name="order",
field=models.IntegerField(default=0, verbose_name='ordre'), field=models.IntegerField(default=0, verbose_name="ordre"),
), ),
] ]

View File

@ -6,13 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0028_auto_20190406_1901'), ("manuels", "0028_auto_20190406_1901"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='consumable', name="consumable",
field=models.BooleanField(choices=[(None, '------------'), (False, 'Non'), (True, 'Oui')], default=None, help_text="Exemple : un cahier d'exercices est un consommable", verbose_name='consommable'), field=models.BooleanField(
choices=[(None, "------------"), (False, "Non"), (True, "Oui")],
default=None,
help_text="Exemple : un cahier d'exercices est un consommable",
verbose_name="consommable",
),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0029_auto_20190406_1904'), ("manuels", "0029_auto_20190406_1904"),
] ]
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
old_name='fields', old_name="fields",
new_name='field', new_name="field",
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0030_auto_20190406_1912'), ("manuels", "0030_auto_20190406_1912"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='field', name="field",
field=models.CharField(max_length=50, verbose_name='disciplines'), field=models.CharField(max_length=50, verbose_name="disciplines"),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0031_auto_20190406_1912'), ("manuels", "0031_auto_20190406_1912"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='suppliesrequirement', model_name="suppliesrequirement",
name='field', name="field",
field=models.CharField(max_length=50, verbose_name='discipline'), field=models.CharField(max_length=50, verbose_name="discipline"),
), ),
] ]

View File

@ -1,19 +1,25 @@
# Generated by Django 2.2 on 2019-04-06 17:19 # Generated by Django 2.2 on 2019-04-06 17:19
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0032_auto_20190406_1913'), ("manuels", "0032_auto_20190406_1913"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='isbn', name="isbn",
field=models.CharField(help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à 13 chiffres (ou EAN)", max_length=20, validators=[manuels.models.isbn_validator], verbose_name='ISBN/EAN'), field=models.CharField(
help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à 13 chiffres (ou EAN)",
max_length=20,
validators=[manuels.models.isbn_validator],
verbose_name="ISBN/EAN",
),
), ),
] ]

View File

@ -6,21 +6,35 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0033_auto_20190406_1919'), ("manuels", "0033_auto_20190406_1919"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='CommonSupply', name="CommonSupply",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='créé le')), "id",
('updated_at', models.DateTimeField(auto_now=True, verbose_name='mis à jour le')), models.AutoField(
('name', models.CharField(max_length=200, verbose_name='nom')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created_at",
models.DateTimeField(auto_now_add=True, verbose_name="créé le"),
),
(
"updated_at",
models.DateTimeField(auto_now=True, verbose_name="mis à jour le"),
),
("name", models.CharField(max_length=200, verbose_name="nom")),
], ],
options={ options={
'verbose_name': 'fourniture commune', "verbose_name": "fourniture commune",
'verbose_name_plural': 'fournitures communes', "verbose_name_plural": "fournitures communes",
}, },
), ),
] ]

View File

@ -6,14 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0034_commonsupply'), ("manuels", "0034_commonsupply"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='commonsupply', model_name="commonsupply",
name='order', name="order",
field=models.IntegerField(default=0, verbose_name='ordre'), field=models.IntegerField(default=0, verbose_name="ordre"),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@ -6,12 +6,16 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0035_commonsupply_order'), ("manuels", "0035_commonsupply_order"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='commonsupply', name="commonsupply",
options={'ordering': ('order',), 'verbose_name': 'fourniture commune', 'verbose_name_plural': 'fournitures communes'}, options={
"ordering": ("order",),
"verbose_name": "fourniture commune",
"verbose_name_plural": "fournitures communes",
},
), ),
] ]

View File

@ -8,18 +8,27 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0036_auto_20190615_1114'), ("manuels", "0036_auto_20190615_1114"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='commonsupply', name="commonsupply",
options={'ordering': ('order', 'name'), 'verbose_name': 'fourniture commune', 'verbose_name_plural': 'fournitures communes'}, options={
"ordering": ("order", "name"),
"verbose_name": "fourniture commune",
"verbose_name_plural": "fournitures communes",
},
), ),
CITextExtension(), CITextExtension(),
migrations.AlterField( migrations.AlterField(
model_name='teacher', model_name="teacher",
name='email', name="email",
field=django.contrib.postgres.fields.citext.CIEmailField(help_text='Utilisée pour vous transmettre votre lien personnel', max_length=254, unique=True, verbose_name='adresse email'), field=django.contrib.postgres.fields.citext.CIEmailField(
help_text="Utilisée pour vous transmettre votre lien personnel",
max_length=254,
unique=True,
verbose_name="adresse email",
),
), ),
] ]

View File

@ -1,19 +1,25 @@
# Generated by Django 3.0.7 on 2021-05-10 06:54 # Generated by Django 3.0.7 on 2021-05-10 06:54
from django.db import migrations, models from django.db import migrations, models
import manuels.models import manuels.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('manuels', '0037_auto_20190627_1905'), ("manuels", "0037_auto_20190627_1905"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='book', model_name="book",
name='isbn', name="isbn",
field=models.CharField(help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à 13 chiffres (ou EAN)", max_length=20, validators=[manuels.models.isbn_validator], verbose_name='ISBN/EAN du manuel élève (hors specimen)'), field=models.CharField(
help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à 13 chiffres (ou EAN)",
max_length=20,
validators=[manuels.models.isbn_validator],
verbose_name="ISBN/EAN du manuel élève (hors specimen)",
),
), ),
] ]

View File

@ -28,101 +28,105 @@ class BaseModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
created_at = models.DateTimeField('créé le', auto_now_add=True) created_at = models.DateTimeField("créé le", auto_now_add=True)
updated_at = models.DateTimeField('mis à jour le', auto_now=True) updated_at = models.DateTimeField("mis à jour le", auto_now=True)
def phone_validator(value): def phone_validator(value):
regex = re.compile(r'^\d{10}$') regex = re.compile(r"^\d{10}$")
if not regex.match(value): if not regex.match(value):
raise ValidationError( raise ValidationError(
"%(value)s n'est pas un numéro de téléphone valide. Format attendu : 10 chiffres.", "%(value)s n'est pas un numéro de téléphone valide. Format attendu : 10 chiffres.",
params={'value': value} params={"value": value},
) )
class Teacher(BaseModel): class Teacher(BaseModel):
class Meta: class Meta:
verbose_name = 'coordonnateur' verbose_name = "coordonnateur"
verbose_name_plural = 'coordonnateurs' verbose_name_plural = "coordonnateurs"
ordering = ['first_name'] ordering = ["first_name"]
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
first_name = models.CharField('prénom', max_length=100) first_name = models.CharField("prénom", max_length=100)
last_name = models.CharField('nom', max_length=100) last_name = models.CharField("nom", max_length=100)
phone_number = models.CharField( phone_number = models.CharField(
'numéro de téléphone', "numéro de téléphone",
help_text="En cas d'urgence, 10 chiffres.", help_text="En cas d'urgence, 10 chiffres.",
max_length=10, max_length=10,
validators=[phone_validator] validators=[phone_validator],
) )
email = CIEmailField( email = CIEmailField(
'adresse email', "adresse email",
help_text='Utilisée pour vous transmettre votre lien personnel', help_text="Utilisée pour vous transmettre votre lien personnel",
unique=True unique=True,
) )
has_confirmed_list = models.BooleanField( has_confirmed_list = models.BooleanField(
'a confirmé les listes', "a confirmé les listes", default=False, blank=True
default=False,
blank=True
) )
def get_absolute_url(self): def get_absolute_url(self):
from django.urls import reverse from django.urls import reverse
return reverse('list_books', kwargs={'pk': str(self.pk)})
return reverse("list_books", kwargs={"pk": str(self.pk)})
@property @property
def full_name(self): def full_name(self):
return f'{self.first_name} {self.last_name}' return f"{self.first_name} {self.last_name}"
def __str__(self): def __str__(self):
return self.full_name return self.full_name
def send_link(self, request): def send_link(self, request):
dest = self.email dest = self.email
link = request.build_absolute_uri(reverse('list_books', args=[str(self.pk)])) link = request.build_absolute_uri(reverse("list_books", args=[str(self.pk)]))
msg = EmailMultiAlternatives( msg = EmailMultiAlternatives(
subject='Gestion des manuels scolaires', subject="Gestion des manuels scolaires",
body=f'Bonjour {self.first_name},\n' body=f"Bonjour {self.first_name},\n"
f'Voici votre lien pour la gestion des manuels scolaires : {link}', f"Voici votre lien pour la gestion des manuels scolaires : {link}",
from_email=settings.SERVER_EMAIL, from_email=settings.SERVER_EMAIL,
to=[dest], to=[dest],
) )
reply_to = [os.getenv('REPLY_TO')] reply_to = [os.getenv("REPLY_TO")]
if reply_to: if reply_to:
msg.reply_to = reply_to msg.reply_to = reply_to
msg.attach_alternative( msg.attach_alternative(
render_to_string('manuels/emails_link.html', {'link': link, 'teacher': self}), "text/html" render_to_string(
"manuels/emails_link.html", {"link": link, "teacher": self}
),
"text/html",
) )
msg.send() msg.send()
def send_confirmation(self, request): def send_confirmation(self, request):
dest = settings.LIBRARIAN_EMAILS dest = settings.LIBRARIAN_EMAILS
link = request.build_absolute_uri(reverse('home_page')) link = request.build_absolute_uri(reverse("home_page"))
msg = EmailMultiAlternatives( msg = EmailMultiAlternatives(
subject="Gestion des manuels scolaires - Confirmation d'un coordonnateur", subject="Gestion des manuels scolaires - Confirmation d'un coordonnateur",
body=f'Bonjour,\n' body=f"Bonjour,\n" f"{self.first_name} a confirmé ses listes sur {link}",
f'{self.first_name} a confirmé ses listes sur {link}',
from_email=settings.SERVER_EMAIL, from_email=settings.SERVER_EMAIL,
to=dest, to=dest,
) )
reply_to = [os.getenv('REPLY_TO')] reply_to = [os.getenv("REPLY_TO")]
if reply_to: if reply_to:
msg.reply_to = reply_to msg.reply_to = reply_to
msg.attach_alternative( msg.attach_alternative(
render_to_string('manuels/emails_confirmation.html', {'link': link, 'teacher': self}), "text/html" render_to_string(
"manuels/emails_confirmation.html", {"link": link, "teacher": self}
),
"text/html",
) )
msg.send() msg.send()
class Level(BaseModel): class Level(BaseModel):
class Meta: class Meta:
verbose_name = 'classe' verbose_name = "classe"
verbose_name_plural = 'classes' verbose_name_plural = "classes"
ordering = ['order', 'name'] ordering = ["order", "name"]
name = models.CharField('nom', max_length=50) name = models.CharField("nom", max_length=50)
order = models.IntegerField('ordre', default=0) order = models.IntegerField("ordre", default=0)
def __str__(self): def __str__(self):
return self.name return self.name
@ -130,7 +134,10 @@ class Level(BaseModel):
@property @property
def non_acquired_consumables(self): def non_acquired_consumables(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return filter(lambda book: book.consumable and not book.previously_acquired, self.prefetched_books) return filter(
lambda book: book.consumable and not book.previously_acquired,
self.prefetched_books,
)
return self.book_set.filter(consumable=True, previously_acquired=False) return self.book_set.filter(consumable=True, previously_acquired=False)
@property @property
@ -143,13 +150,17 @@ class Level(BaseModel):
def non_acquired_consumable_price(self): def non_acquired_consumable_price(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return sum(map(lambda book: book.price, self.non_acquired_consumables)) return sum(map(lambda book: book.price, self.non_acquired_consumables))
return self.non_acquired_consumables.aggregate(Sum('price')).get( return self.non_acquired_consumables.aggregate(Sum("price")).get(
'price__sum', 0) "price__sum", 0
)
@property @property
def non_acquired_books(self): def non_acquired_books(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return filter(lambda book: not book.consumable and not book.previously_acquired, self.prefetched_books) return filter(
lambda book: not book.consumable and not book.previously_acquired,
self.prefetched_books,
)
return self.book_set.filter(consumable=False, previously_acquired=False) return self.book_set.filter(consumable=False, previously_acquired=False)
@property @property
@ -162,39 +173,41 @@ class Level(BaseModel):
def non_acquired_book_price(self): def non_acquired_book_price(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return sum(map(lambda book: book.price, self.non_acquired_books)) return sum(map(lambda book: book.price, self.non_acquired_books))
return self.non_acquired_books.aggregate(Sum('price')).get( return self.non_acquired_books.aggregate(Sum("price")).get("price__sum", 0)
'price__sum', 0)
@property @property
def non_acquired_items(self): def non_acquired_items(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return filter(lambda book: not book.previously_acquired, self.prefetched_books) return filter(
lambda book: not book.previously_acquired, self.prefetched_books
)
return self.book_set.filter(previously_acquired=False) return self.book_set.filter(previously_acquired=False)
@property @property
def non_acquired_total_price(self): def non_acquired_total_price(self):
if hasattr(self, "prefetched_books"): if hasattr(self, "prefetched_books"):
return sum(map(lambda book: book.price, self.non_acquired_items)) return sum(map(lambda book: book.price, self.non_acquired_items))
return self.non_acquired_items.aggregate(Sum('price')).get( return self.non_acquired_items.aggregate(Sum("price")).get("price__sum", 0)
'price__sum', 0)
class Editor(BaseModel): class Editor(BaseModel):
class Meta: class Meta:
verbose_name = 'éditeur' verbose_name = "éditeur"
verbose_name_plural = 'éditeurs' verbose_name_plural = "éditeurs"
ordering = ['name'] ordering = ["name"]
name = models.CharField('nom', max_length=100) name = models.CharField("nom", max_length=100)
def __str__(self): def __str__(self):
return self.name return self.name
def isbn_validator(value): def isbn_validator(value):
regex = re.compile(r'(\d-?){10,13}X?') regex = re.compile(r"(\d-?){10,13}X?")
if not regex.match(value): if not regex.match(value):
raise ValidationError("%(value)s n'est pas un ISBN valide.", params={'value': value}) raise ValidationError(
"%(value)s n'est pas un ISBN valide.", params={"value": value}
)
def positive_float_validator(value): def positive_float_validator(value):
@ -209,30 +222,36 @@ def positive_float_validator(value):
class Book(BaseModel): class Book(BaseModel):
class Meta: class Meta:
verbose_name = 'livre' verbose_name = "livre"
verbose_name_plural = 'livres' verbose_name_plural = "livres"
teacher = models.ForeignKey(verbose_name='coordonnateur', to=Teacher, on_delete=models.PROTECT, null=True) teacher = models.ForeignKey(
level = models.ForeignKey(verbose_name='classe', to=Level, on_delete=models.PROTECT, null=True) verbose_name="coordonnateur", to=Teacher, on_delete=models.PROTECT, null=True
field = models.CharField('discipline', max_length=200) )
title = models.TextField('titre') level = models.ForeignKey(
authors = models.TextField('auteurs') verbose_name="classe", to=Level, on_delete=models.PROTECT, null=True
editor = models.ForeignKey(verbose_name='éditeur', to=Editor, on_delete=models.PROTECT, null=True) )
other_editor = models.CharField(verbose_name='préciser', max_length=100, blank=True) field = models.CharField("discipline", max_length=200)
publication_year = models.PositiveIntegerField('année de publication') title = models.TextField("titre")
authors = models.TextField("auteurs")
editor = models.ForeignKey(
verbose_name="éditeur", to=Editor, on_delete=models.PROTECT, null=True
)
other_editor = models.CharField(verbose_name="préciser", max_length=100, blank=True)
publication_year = models.PositiveIntegerField("année de publication")
isbn = models.CharField( isbn = models.CharField(
'ISBN/EAN du manuel élève (hors specimen)', "ISBN/EAN du manuel élève (hors specimen)",
max_length=20, max_length=20,
help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement " help_text="Format attendu : 10 ou 13 chiffres, éventuellement séparés par des tirets et éventuellement "
"suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à " "suivis de la lettre <code>X</code>. La recherche sur Decitre ne fonctionnera qu'avec un code ISBN à "
"13 chiffres (ou EAN)", "13 chiffres (ou EAN)",
validators=[isbn_validator] validators=[isbn_validator],
) )
price = models.FloatField('prix', validators=[positive_float_validator]) price = models.FloatField("prix", validators=[positive_float_validator])
YES_NO_CHOICE = ( YES_NO_CHOICE = (
(None, '------------'), (None, "------------"),
(False, 'Non'), (False, "Non"),
(True, 'Oui'), (True, "Oui"),
) )
previously_acquired = models.BooleanField( previously_acquired = models.BooleanField(
"manuel acquis précédemment par l'élève", "manuel acquis précédemment par l'élève",
@ -240,18 +259,14 @@ class Book(BaseModel):
blank=False, blank=False,
default=None, default=None,
) )
done = models.BooleanField( done = models.BooleanField("Traité", blank=True, default=False)
'Traité',
blank=True,
default=False
)
comments = models.TextField( comments = models.TextField(
'commentaires', "commentaires",
blank=True, blank=True,
help_text="Ce message sera visible par la documentaliste." help_text="Ce message sera visible par la documentaliste.",
) )
consumable = models.BooleanField( consumable = models.BooleanField(
'consommable', "consommable",
help_text="Exemple : un cahier d'exercices est un consommable", help_text="Exemple : un cahier d'exercices est un consommable",
choices=YES_NO_CHOICE, choices=YES_NO_CHOICE,
blank=False, blank=False,
@ -261,32 +276,35 @@ class Book(BaseModel):
@property @property
def previously_acquired_text(self): def previously_acquired_text(self):
if self.previously_acquired: if self.previously_acquired:
return 'Oui' return "Oui"
else: else:
return 'Non' return "Non"
@property @property
def consumable_text(self): def consumable_text(self):
if self.consumable: if self.consumable:
return 'Oui' return "Oui"
else: else:
return 'Non' return "Non"
def __str__(self): def __str__(self):
return f'{self.title} ({self.authors}) - {self.isbn}' return f"{self.title} ({self.authors}) - {self.isbn}"
def get_absolute_url(self): def get_absolute_url(self):
from django.urls import reverse from django.urls import reverse
return reverse('edit_book', kwargs={'teacher_pk': str(self.teacher.pk), 'pk': str(self.pk)})
return reverse(
"edit_book", kwargs={"teacher_pk": str(self.teacher.pk), "pk": str(self.pk)}
)
def update_from_decitre(self): def update_from_decitre(self):
decitre_data = self.fetch_from_decitre(self.isbn) decitre_data = self.fetch_from_decitre(self.isbn)
self.title = decitre_data.get('title') self.title = decitre_data.get("title")
self.authors = decitre_data.get('authors') self.authors = decitre_data.get("authors")
self.price = decitre_data.get('price') self.price = decitre_data.get("price")
self.publication_year = decitre_data.get('year') self.publication_year = decitre_data.get("year")
editor = decitre_data.get('editor') editor = decitre_data.get("editor")
potential_editor = ( potential_editor = (
Editor.objects.filter(name__iexact=editor).first() Editor.objects.filter(name__iexact=editor).first()
or Editor.objects.filter(name__icontains=editor).first() or Editor.objects.filter(name__icontains=editor).first()
@ -302,31 +320,35 @@ class Book(BaseModel):
@property @property
def decitre_url(self): def decitre_url(self):
isbn = self.isbn.strip().replace('-', '') isbn = self.isbn.strip().replace("-", "")
if not validate_isbn(isbn) or len(isbn) == 10: if not validate_isbn(isbn) or len(isbn) == 10:
return "" return ""
return f'https://www.decitre.fr/livres/{isbn}.html' return f"https://www.decitre.fr/livres/{isbn}.html"
@staticmethod @staticmethod
def fetch_from_decitre(isbn: str): def fetch_from_decitre(isbn: str):
isbn = isbn.strip().replace('-', '') isbn = isbn.strip().replace("-", "")
if not validate_isbn(isbn): if not validate_isbn(isbn):
raise ISBNError({ raise ISBNError({"error": "L'ISBN saisi n'est pas valide."})
'error': "L'ISBN saisi n'est pas valide."
})
if len(isbn) == 10: if len(isbn) == 10:
raise ISBNError({ raise ISBNError(
'error': "La recherche sur Decitre ne fonctionne qu'avec un ISBN 13 (ou EAN)." {
}) "error": "La recherche sur Decitre ne fonctionne qu'avec un ISBN 13 (ou EAN)."
}
)
try: try:
res = requests.get(f'https://www.decitre.fr/livres/{isbn}.html', timeout=10) res = requests.get(f"https://www.decitre.fr/livres/{isbn}.html", timeout=10)
except requests.exceptions.Timeout as exc: except requests.exceptions.Timeout as exc:
raise ISBNError({ raise ISBNError(
'error': "Decitre n'a pas répondu dans les temps. Message : {}".format(str(exc)) {
}) "error": "Decitre n'a pas répondu dans les temps. Message : {}".format(
str(exc)
)
}
)
try: try:
res.raise_for_status() res.raise_for_status()
@ -334,89 +356,92 @@ class Book(BaseModel):
message = ( message = (
"Erreur lors de la recherche. Il se peut que le livre n'existe pas dans la base de connaissances " "Erreur lors de la recherche. Il se peut que le livre n'existe pas dans la base de connaissances "
"de Decitre ou que vous ayez mal saisi l'ISBN. Vous pouvez toujours saisir " "de Decitre ou que vous ayez mal saisi l'ISBN. Vous pouvez toujours saisir "
"les informations du livre à la main. Message : {}").format(str(exc)) "les informations du livre à la main. Message : {}"
raise ISBNError({ ).format(str(exc))
'error': message raise ISBNError({"error": message})
})
decitre_soup = bs4.BeautifulSoup(res.text, "html.parser") decitre_soup = bs4.BeautifulSoup(res.text, "html.parser")
title = decitre_soup.select('h1.product-title') title = decitre_soup.select("h1.product-title")
if title: if title:
title = title[0] title = title[0]
if title.span: if title.span:
title.span.extract() title.span.extract()
title = title.get_text(strip=True) title = title.get_text(strip=True)
authors = decitre_soup.select('.authors') authors = decitre_soup.select(".authors")
if authors: if authors:
authors = authors[0] authors = authors[0]
authors = authors.get_text(strip=True) authors = authors.get_text(strip=True)
price = decitre_soup.select('.fp-top--add-to-cart div.price span.final-price') price = decitre_soup.select(".fp-top--add-to-cart div.price span.final-price")
if price: if price:
price = price[0] price = price[0]
price = price.get_text().replace('', '').replace(',', '.').strip() price = price.get_text().replace("", "").replace(",", ".").strip()
year = None year = None
editor = None editor = None
extra_info = decitre_soup.select('.informations-container') extra_info = decitre_soup.select(".informations-container")
logger.debug('raw extra_info') logger.debug("raw extra_info")
logger.debug(extra_info) logger.debug(extra_info)
if not extra_info: if not extra_info:
logger.debug('#fiche-technique') logger.debug("#fiche-technique")
extra_info = decitre_soup.select('#fiche-technique') extra_info = decitre_soup.select("#fiche-technique")
logger.debug('raw extra_info fiche technique') logger.debug("raw extra_info fiche technique")
logger.debug(extra_info) logger.debug(extra_info)
if extra_info: if extra_info:
extra_info = extra_info[0].get_text(strip=True) extra_info = extra_info[0].get_text(strip=True)
logger.debug('extra_info') logger.debug("extra_info")
logger.debug(extra_info) logger.debug(extra_info)
matches = re.search( matches = re.search(
r'Date de parution(?: :)?\d{2}/\d{2}/(?P<year>\d{4})Editeur(?: :)?(?P<editor>.+?)(?:ISBN|Collection)', r"Date de parution(?: :)?\d{2}/\d{2}/(?P<year>\d{4})Editeur(?: :)?(?P<editor>.+?)(?:ISBN|Collection)",
extra_info) extra_info,
)
if matches: if matches:
groups = matches.groupdict() groups = matches.groupdict()
year = groups.get('year') year = groups.get("year")
editor = groups.get('editor').strip() editor = groups.get("editor").strip()
return { return {
'title': title, "title": title,
'authors': authors, "authors": authors,
'isbn': isbn, "isbn": isbn,
'price': float(price) if price else None, "price": float(price) if price else None,
'year': year, "year": year,
'editor': editor, "editor": editor,
} }
class SuppliesRequirement(BaseModel): class SuppliesRequirement(BaseModel):
class Meta: class Meta:
verbose_name = 'demande de fournitures' verbose_name = "demande de fournitures"
verbose_name_plural = 'demandes de fournitures' verbose_name_plural = "demandes de fournitures"
teacher = models.ForeignKey(verbose_name='coordonnateur', to=Teacher, on_delete=models.PROTECT, null=True) teacher = models.ForeignKey(
level = models.ForeignKey(verbose_name='classe', to=Level, on_delete=models.PROTECT, null=True) verbose_name="coordonnateur", to=Teacher, on_delete=models.PROTECT, null=True
field = models.CharField('discipline', max_length=50)
supplies = models.TextField('fournitures')
done = models.BooleanField(
'Traité',
blank=True,
default=False
) )
level = models.ForeignKey(
verbose_name="classe", to=Level, on_delete=models.PROTECT, null=True
)
field = models.CharField("discipline", max_length=50)
supplies = models.TextField("fournitures")
done = models.BooleanField("Traité", blank=True, default=False)
def __str__(self): def __str__(self):
return f'{self.supplies} pour {self.level} ({self.teacher})' return f"{self.supplies} pour {self.level} ({self.teacher})"
class CommonSupply(BaseModel): class CommonSupply(BaseModel):
class Meta: class Meta:
verbose_name = 'fourniture commune' verbose_name = "fourniture commune"
verbose_name_plural = 'fournitures communes' verbose_name_plural = "fournitures communes"
ordering = ('order', 'name',) ordering = (
"order",
"name",
)
name = models.CharField('nom', max_length=200) name = models.CharField("nom", max_length=200)
order = models.IntegerField('ordre') order = models.IntegerField("ordre")
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -1 +0,0 @@

View File

@ -112,4 +112,4 @@
{% block end_js %} {% block end_js %}
<script defer src="{% static 'manuels/no_book.js' %}"></script> <script defer src="{% static 'manuels/no_book.js' %}"></script>
<script defer src="{% static 'manuels/fetch-isbn.js' %}"></script> <script defer src="{% static 'manuels/fetch-isbn.js' %}"></script>
{% endblock %} {% endblock %}

View File

@ -7,4 +7,4 @@
<p class="mb-0"> <p class="mb-0">
{{ object }} {{ object }}
</p> </p>
</div> </div>

View File

@ -28,4 +28,4 @@
</form> </form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -144,4 +144,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -8,4 +8,4 @@
<li>{{ supply.name }}</li> <li>{{ supply.name }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>

View File

@ -4,4 +4,3 @@ from django.test import TestCase
class MyTestCase(TestCase): class MyTestCase(TestCase):
def test_something(self): def test_something(self):
self.assertEqual(True, False) self.assertEqual(True, False)

View File

@ -1,24 +1,57 @@
from django.conf import settings from django.conf import settings
from django.urls import path, include from django.urls import include, path
from manuels.views import AddBookView, ListBooksView, clear_teacher_view, AddSuppliesView, EditBookView, \ from manuels.views import (
EditSuppliesView, DeleteBookView, DeleteSuppliesView, ConfirmTeacherView, isbn_api AddBookView,
AddSuppliesView,
ConfirmTeacherView,
DeleteBookView,
DeleteSuppliesView,
EditBookView,
EditSuppliesView,
ListBooksView,
clear_teacher_view,
isbn_api,
)
urlpatterns = [ urlpatterns = [
path('teacher/<uuid:pk>/add_book', AddBookView.as_view(), name='add_book'), path("teacher/<uuid:pk>/add_book", AddBookView.as_view(), name="add_book"),
path('teacher/<uuid:pk>/add_supplies', AddSuppliesView.as_view(), name='add_supplies'), path(
path('teacher/<uuid:pk>', ListBooksView.as_view(), name='list_books'), "teacher/<uuid:pk>/add_supplies", AddSuppliesView.as_view(), name="add_supplies"
path('teacher/<uuid:teacher_pk>/book/<int:pk>', EditBookView.as_view(), name='edit_book'), ),
path('teacher/<uuid:teacher_pk>/book/<int:pk>/delete', DeleteBookView.as_view(), name='delete_book'), path("teacher/<uuid:pk>", ListBooksView.as_view(), name="list_books"),
path('teacher/<uuid:teacher_pk>/supplies/<int:pk>', EditSuppliesView.as_view(), name='edit_supplies'), path(
path('teacher/<uuid:teacher_pk>/supplies/<int:pk>/delete', DeleteSuppliesView.as_view(), name='delete_supplies'), "teacher/<uuid:teacher_pk>/book/<int:pk>",
path('teacher/<uuid:pk>/confirm', ConfirmTeacherView.as_view(), name='confirm_teacher'), EditBookView.as_view(),
path('clear', clear_teacher_view, name='clear_teacher'), name="edit_book",
path('isbn_api/<str:isbn>', isbn_api, name='isbn_api'), ),
path(
"teacher/<uuid:teacher_pk>/book/<int:pk>/delete",
DeleteBookView.as_view(),
name="delete_book",
),
path(
"teacher/<uuid:teacher_pk>/supplies/<int:pk>",
EditSuppliesView.as_view(),
name="edit_supplies",
),
path(
"teacher/<uuid:teacher_pk>/supplies/<int:pk>/delete",
DeleteSuppliesView.as_view(),
name="delete_supplies",
),
path(
"teacher/<uuid:pk>/confirm",
ConfirmTeacherView.as_view(),
name="confirm_teacher",
),
path("clear", clear_teacher_view, name="clear_teacher"),
path("isbn_api/<str:isbn>", isbn_api, name="isbn_api"),
] ]
if settings.DEBUG: if settings.DEBUG:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)), path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns ] + urlpatterns

View File

@ -2,7 +2,7 @@ def validate_isbn(isbn):
_sum = 0 _sum = 0
if len(isbn) == 10: if len(isbn) == 10:
for i, digit in enumerate(isbn): for i, digit in enumerate(isbn):
if digit == 'X': if digit == "X":
digit = 10 digit = 10
else: else:
digit = int(digit) digit = int(digit)

View File

@ -8,10 +8,10 @@ from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.views.generic import CreateView, UpdateView, DeleteView, TemplateView from django.views.generic import CreateView, DeleteView, TemplateView, UpdateView
from manuels.forms import AddBookForm, AddSuppliesForm, EditBookForm, EditSuppliesForm from manuels.forms import AddBookForm, AddSuppliesForm, EditBookForm, EditSuppliesForm
from manuels.models import Teacher, Book, SuppliesRequirement, CommonSupply, ISBNError from manuels.models import Book, CommonSupply, ISBNError, SuppliesRequirement, Teacher
from manuels.utils import validate_isbn from manuels.utils import validate_isbn
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,13 +19,13 @@ logger = logging.getLogger(__name__)
class HomePageView(CreateView): class HomePageView(CreateView):
model = Teacher model = Teacher
fields = ['first_name', 'last_name', 'phone_number', 'email'] fields = ["first_name", "last_name", "phone_number", "email"]
template_name = 'manuels/home_page.html' template_name = "manuels/home_page.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
teacher_pk = request.session.get('teacher_pk') teacher_pk = request.session.get("teacher_pk")
if teacher_pk: if teacher_pk:
return redirect('list_books', pk=teacher_pk) return redirect("list_books", pk=teacher_pk)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -37,26 +37,31 @@ class HomePageView(CreateView):
class BaseTeacherView: class BaseTeacherView:
teacher = None teacher = None
teacher_field = 'pk' teacher_field = "pk"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.teacher = Teacher.objects.filter(pk=self.kwargs[self.teacher_field]).first() self.teacher = Teacher.objects.filter(
pk=self.kwargs[self.teacher_field]
).first()
if not self.teacher: if not self.teacher:
messages.warning(request, "Impossible de trouver le coordonnateur demandé. Si vous pensez que ceci est " messages.warning(
"une erreur, merci de vous adresser à votre documentaliste.") request,
return redirect('clear_teacher') "Impossible de trouver le coordonnateur demandé. Si vous pensez que ceci est "
request.session['teacher_pk'] = str(self.teacher.pk) "une erreur, merci de vous adresser à votre documentaliste.",
)
return redirect("clear_teacher")
request.session["teacher_pk"] = str(self.teacher.pk)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data() context = super().get_context_data()
context['teacher'] = self.teacher context["teacher"] = self.teacher
return context return context
class ListBooksView(BaseTeacherView, TemplateView): class ListBooksView(BaseTeacherView, TemplateView):
template_name = 'manuels/list_books_supplies.html' template_name = "manuels/list_books_supplies.html"
class ItemView(BaseTeacherView): class ItemView(BaseTeacherView):
@ -66,79 +71,80 @@ class ItemView(BaseTeacherView):
success_target = None success_target = None
message_template = None message_template = None
verb = None verb = None
button_class = 'primary' button_class = "primary"
button_icon = 'fas fa-check-circle' button_icon = "fas fa-check-circle"
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs) response = super().dispatch(request, *args, **kwargs)
if self.teacher and self.teacher.has_confirmed_list: if self.teacher and self.teacher.has_confirmed_list:
messages.error(request, "Vous avez déjà confirmé vos listes. Il n'est plus possible de les modifier.") messages.error(
return redirect('list_books', pk=self.teacher.pk) request,
"Vous avez déjà confirmé vos listes. Il n'est plus possible de les modifier.",
)
return redirect("list_books", pk=self.teacher.pk)
return response return response
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['item'] = self.item_text context["item"] = self.item_text
context['item_plural'] = self.item_text_plural context["item_plural"] = self.item_text_plural
context['message_template'] = self.message_template context["message_template"] = self.message_template
context['verb'] = self.verb context["verb"] = self.verb
context['button_class'] = self.button_class context["button_class"] = self.button_class
context['button_icon'] = self.button_icon context["button_icon"] = self.button_icon
return context return context
def get_initial(self): def get_initial(self):
return { return {"teacher": self.teacher}
'teacher': self.teacher
}
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
form.fields['teacher'].queryset = Teacher.objects.filter(pk=self.teacher.pk) form.fields["teacher"].queryset = Teacher.objects.filter(pk=self.teacher.pk)
return form return form
class AddItemView(ItemView, CreateView): class AddItemView(ItemView, CreateView):
verb = 'Ajouter' verb = "Ajouter"
button_icon = 'fas fa-plus-circle' button_icon = "fas fa-plus-circle"
def get_success_url(self): def get_success_url(self):
if self.add_another: if self.add_another:
return reverse(self.success_target, args=[str(self.teacher.pk)]) return reverse(self.success_target, args=[str(self.teacher.pk)])
else: else:
return reverse('list_books', args=[str(self.teacher.pk)]) return reverse("list_books", args=[str(self.teacher.pk)])
def form_valid(self, form): def form_valid(self, form):
self.add_another = form.cleaned_data['add_another'] self.add_another = form.cleaned_data["add_another"]
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
class BookView: class BookView:
model = Book model = Book
success_target = 'add_book' success_target = "add_book"
item_text = 'un livre' item_text = "un livre"
item_text_plural = 'livres' item_text_plural = "livres"
class AddBookView(BookView, AddItemView): class AddBookView(BookView, AddItemView):
form_class = AddBookForm form_class = AddBookForm
template_name = 'manuels/add_book.html' template_name = "manuels/add_book.html"
def form_valid(self, form: AddBookForm): def form_valid(self, form: AddBookForm):
for level in form.cleaned_data['levels']: for level in form.cleaned_data["levels"]:
book = Book.objects.create( book = Book.objects.create(
teacher=form.cleaned_data['teacher'], teacher=form.cleaned_data["teacher"],
level=level, level=level,
field=form.cleaned_data['field'], field=form.cleaned_data["field"],
title=form.cleaned_data['title'], title=form.cleaned_data["title"],
authors=form.cleaned_data['authors'], authors=form.cleaned_data["authors"],
editor=form.cleaned_data['editor'], editor=form.cleaned_data["editor"],
other_editor=form.cleaned_data['other_editor'], other_editor=form.cleaned_data["other_editor"],
publication_year=form.cleaned_data['publication_year'], publication_year=form.cleaned_data["publication_year"],
isbn=form.cleaned_data['isbn'], isbn=form.cleaned_data["isbn"],
price=form.cleaned_data['price'], price=form.cleaned_data["price"],
previously_acquired=form.cleaned_data['previously_acquired'], previously_acquired=form.cleaned_data["previously_acquired"],
comments=form.cleaned_data['comments'], comments=form.cleaned_data["comments"],
consumable=form.cleaned_data['consumable'], consumable=form.cleaned_data["consumable"],
) )
messages.success(self.request, f'"{book}" a été ajouté.') messages.success(self.request, f'"{book}" a été ajouté.')
@ -147,28 +153,28 @@ class AddBookView(BookView, AddItemView):
class SuppliesView: class SuppliesView:
model = SuppliesRequirement model = SuppliesRequirement
success_target = 'add_supplies' success_target = "add_supplies"
item_text = 'des fournitures' item_text = "des fournitures"
item_text_plural = 'fournitures' item_text_plural = "fournitures"
class AddSuppliesView(SuppliesView, AddItemView): class AddSuppliesView(SuppliesView, AddItemView):
form_class = AddSuppliesForm form_class = AddSuppliesForm
message_template = 'manuels/supplies_message.html' message_template = "manuels/supplies_message.html"
template_name = 'manuels/add_supplies.html' template_name = "manuels/add_supplies.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['common_supplies'] = CommonSupply.objects.all() context["common_supplies"] = CommonSupply.objects.all()
return context return context
def form_valid(self, form: AddBookForm): def form_valid(self, form: AddBookForm):
for level in form.cleaned_data['levels']: for level in form.cleaned_data["levels"]:
supplies = SuppliesRequirement.objects.create( supplies = SuppliesRequirement.objects.create(
teacher=form.cleaned_data['teacher'], teacher=form.cleaned_data["teacher"],
level=level, level=level,
field=form.cleaned_data['field'], field=form.cleaned_data["field"],
supplies=form.cleaned_data['supplies'], supplies=form.cleaned_data["supplies"],
) )
messages.success(self.request, f'"{supplies}" a été ajouté.') messages.success(self.request, f'"{supplies}" a été ajouté.')
@ -176,46 +182,46 @@ class AddSuppliesView(SuppliesView, AddItemView):
class EditItemView(ItemView, UpdateView): class EditItemView(ItemView, UpdateView):
teacher_field = 'teacher_pk' teacher_field = "teacher_pk"
item_text = None item_text = None
item_text_plural = None item_text_plural = None
message_template = None message_template = None
verb = 'Modifier' verb = "Modifier"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(teacher=self.teacher) return self.model.objects.filter(teacher=self.teacher)
def get_success_url(self): def get_success_url(self):
messages.success(self.request, f'"{self.object}" a été modifié.') messages.success(self.request, f'"{self.object}" a été modifié.')
return reverse('list_books', args=[str(self.teacher.pk)]) return reverse("list_books", args=[str(self.teacher.pk)])
class EditBookView(BookView, EditItemView): class EditBookView(BookView, EditItemView):
form_class = EditBookForm form_class = EditBookForm
template_name = 'manuels/add_book.html' template_name = "manuels/add_book.html"
class EditSuppliesView(SuppliesView, EditItemView): class EditSuppliesView(SuppliesView, EditItemView):
form_class = EditSuppliesForm form_class = EditSuppliesForm
template_name = 'manuels/add_supplies.html' template_name = "manuels/add_supplies.html"
class DeleteItemView(ItemView, DeleteView): class DeleteItemView(ItemView, DeleteView):
teacher_field = 'teacher_pk' teacher_field = "teacher_pk"
item_text = None item_text = None
item_text_plural = None item_text_plural = None
message_template = 'manuels/confirm_delete.html' message_template = "manuels/confirm_delete.html"
verb = 'Supprimer' verb = "Supprimer"
button_class = 'danger' button_class = "danger"
button_icon = 'fas fa-trash' button_icon = "fas fa-trash"
template_name = 'manuels/add_supplies.html' template_name = "manuels/add_supplies.html"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(teacher=self.teacher) return self.model.objects.filter(teacher=self.teacher)
def get_success_url(self): def get_success_url(self):
messages.success(self.request, f'"{self.object}" a été supprimé.') messages.success(self.request, f'"{self.object}" a été supprimé.')
return reverse('list_books', args=[str(self.teacher.pk)]) return reverse("list_books", args=[str(self.teacher.pk)])
class DeleteBookView(BookView, DeleteItemView): class DeleteBookView(BookView, DeleteItemView):
@ -227,22 +233,25 @@ class DeleteSuppliesView(SuppliesView, DeleteItemView):
def clear_teacher_view(request): def clear_teacher_view(request):
if 'teacher_pk' in request.session: if "teacher_pk" in request.session:
del request.session['teacher_pk'] del request.session["teacher_pk"]
return redirect('home_page') return redirect("home_page")
class ConfirmTeacherView(BaseTeacherView, UpdateView): class ConfirmTeacherView(BaseTeacherView, UpdateView):
model = Teacher model = Teacher
fields = [] fields = []
template_name = 'manuels/confirm_teacher.html' template_name = "manuels/confirm_teacher.html"
def form_valid(self, form): def form_valid(self, form):
response = super().form_valid(form) response = super().form_valid(form)
self.object.has_confirmed_list = True self.object.has_confirmed_list = True
self.object.save() self.object.save()
self.object.send_confirmation(request=self.request) self.object.send_confirmation(request=self.request)
messages.success(self.request, "Vos listes ont été validées. Votre documentaliste a été notifiée par email.") messages.success(
self.request,
"Vos listes ont été validées. Votre documentaliste a été notifiée par email.",
)
return response return response

View File

@ -13,9 +13,9 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
import os import os
from pathlib import Path from pathlib import Path
import environ
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
import environ
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
env = environ.Env( env = environ.Env(
@ -27,8 +27,8 @@ env = environ.Env(
SERVER_EMAIL=str, SERVER_EMAIL=str,
AUTHORIZED_EMAILS=(list, []), AUTHORIZED_EMAILS=(list, []),
LIBRARIAN_EMAILS=(list, []), LIBRARIAN_EMAILS=(list, []),
MAILGUN_ACCESS_KEY=(str, ''), MAILGUN_ACCESS_KEY=(str, ""),
MAILGUN_SERVER_NAME=(str, ''), MAILGUN_SERVER_NAME=(str, ""),
) )
env_file = os.getenv("ENV_FILE", None) env_file = os.getenv("ENV_FILE", None)
if env_file: if env_file:
@ -39,36 +39,35 @@ if env_file:
SECRET_KEY = env("SECRET_KEY") SECRET_KEY = env("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DJANGO_ENV') == 'dev' DEBUG = env("DJANGO_ENV") == "dev"
ALLOWED_HOSTS = ['web', '127.0.0.1'] ALLOWED_HOSTS = ["web", "127.0.0.1"]
if DEBUG: if DEBUG:
ALLOWED_HOSTS.extend([ ALLOWED_HOSTS.extend(["localhost", env("CURRENT_IP")])
'localhost',
env('CURRENT_IP')
])
ALLOWED_HOSTS.extend(env("HOST")) ALLOWED_HOSTS.extend(env("HOST"))
ADMINS = [('Gabriel', env('ADMIN_EMAIL')), ] ADMINS = [
SERVER_EMAIL = env('SERVER_EMAIL') ("Gabriel", env("ADMIN_EMAIL")),
EMAIL_SUBJECT_PREFIX = '[Manuels] ' ]
SERVER_EMAIL = env("SERVER_EMAIL")
EMAIL_SUBJECT_PREFIX = "[Manuels] "
AUTHORIZED_EMAILS = env('AUTHORIZED_EMAILS') AUTHORIZED_EMAILS = env("AUTHORIZED_EMAILS")
LIBRARIAN_EMAILS = env('LIBRARIAN_EMAILS') LIBRARIAN_EMAILS = env("LIBRARIAN_EMAILS")
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'anymail', "anymail",
'bootstrap4', "bootstrap4",
'manuels', "manuels",
'import_export', "import_export",
] ]
if DEBUG: if DEBUG:
@ -77,47 +76,47 @@ if DEBUG:
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'whitenoise.middleware.WhiteNoiseMiddleware', "whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
if DEBUG: if DEBUG:
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
ROOT_URLCONF = 'manuels_collection.urls' ROOT_URLCONF = "manuels_collection.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
'manuels.context_processors.authorized_mails' "manuels.context_processors.authorized_mails",
], ],
}, },
}, },
] ]
WSGI_APPLICATION = 'manuels_collection.wsgi.application' WSGI_APPLICATION = "manuels_collection.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
default_db_path = BASE_DIR / 'db.sqlite3' default_db_path = BASE_DIR / "db.sqlite3"
DATABASES = { DATABASES = {
'default': env.db(default=f'sqlite:///{default_db_path}'), "default": env.db(default=f"sqlite:///{default_db_path}"),
} }
# Password validation # Password validation
@ -125,16 +124,16 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
@ -145,9 +144,9 @@ INTERNAL_IPS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/ # https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'fr-fr' LANGUAGE_CODE = "fr-fr"
TIME_ZONE = 'Europe/Paris' TIME_ZONE = "Europe/Paris"
USE_I18N = True USE_I18N = True
@ -156,53 +155,47 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# Logging # Logging
LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO' LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'formatters': { "formatters": {
'verbose': { "verbose": {
'format': '[%(asctime)s] [%(process)d] [%(levelname)s] %(module)s - %(message)s' "format": "[%(asctime)s] [%(process)d] [%(levelname)s] %(module)s - %(message)s"
}, },
}, },
'handlers': { "handlers": {
'console': { "console": {"class": "logging.StreamHandler", "formatter": "verbose"},
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
}, },
'loggers': { "loggers": {
'manuels': { "manuels": {"handlers": ["console"], "level": LOG_LEVEL},
'handlers': ['console'],
'level': LOG_LEVEL
},
}, },
} }
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/ # https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / 'staticfiles' STATIC_ROOT = BASE_DIR / "staticfiles"
LOGIN_REDIRECT_URL = 'rooms-list' LOGIN_REDIRECT_URL = "rooms-list"
ANYMAIL = { ANYMAIL = {
"MAILGUN_API_KEY": env('MAILGUN_ACCESS_KEY'), "MAILGUN_API_KEY": env("MAILGUN_ACCESS_KEY"),
"MAILGUN_SENDER_DOMAIN": env('MAILGUN_SERVER_NAME'), "MAILGUN_SENDER_DOMAIN": env("MAILGUN_SERVER_NAME"),
} }
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
MESSAGE_TAGS = { MESSAGE_TAGS = {
messages.ERROR: 'danger', messages.ERROR: "danger",
} }
CACHES = { CACHES = {
'default': { "default": {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache', "BACKEND": "django.core.cache.backends.db.DatabaseCache",
'LOCATION': 'manuels_cache', "LOCATION": "manuels_cache",
} }
} }
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -14,14 +14,14 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import include, path
from manuels.views import HomePageView from manuels.views import HomePageView
admin.site.site_header = 'Manuels scolaires - Administration' admin.site.site_header = "Manuels scolaires - Administration"
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
path('', HomePageView.as_view(), name='home_page'), path("", HomePageView.as_view(), name="home_page"),
path('', include('manuels.urls')), path("", include("manuels.urls")),
] ]