style(pre-commit): Add hook

Python:
- black
- isort (black profile)
- ruff

Nix:
- statix
- nixfmt-rfc-style
- deadnix
This commit is contained in:
sinavir 2024-07-04 20:26:24 +02:00
parent 4d99ba9026
commit 8048c2593d
53 changed files with 775 additions and 267 deletions

2
.gitignore vendored
View file

@ -68,3 +68,5 @@ public/
# Vim recover files
*~
.pre-commit-config.yaml

View file

@ -1,5 +1,5 @@
from django.forms import ModelForm, ValidationError
from django.contrib.auth.models import User
from django.forms import ModelForm, ValidationError
class AccountSettingsForm(ModelForm):

View file

@ -8,27 +8,94 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
name='User',
name="User",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='adresse email')),
('public_name', models.CharField(help_text='Ce nom est utilisé pour toutes les interactions publiques sur GestioJeux. Il doit être unique.', max_length=150, unique=True, verbose_name='nom ou pseudo')),
('is_staff', models.BooleanField(default=False, help_text="Précise si lutilisateur peut se connecter à ce site d'administration.", verbose_name='statut équipe')),
('is_active', models.BooleanField(default=True, help_text='Précise si lutilisateur doit être considéré comme actif. Décochez ceci plutôt que de supprimer le compte.', verbose_name='actif')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"email",
models.EmailField(
max_length=254, unique=True, verbose_name="adresse email"
),
),
(
"public_name",
models.CharField(
help_text="Ce nom est utilisé pour toutes les interactions publiques sur GestioJeux. Il doit être unique.",
max_length=150,
unique=True,
verbose_name="nom ou pseudo",
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Précise si lutilisateur peut se connecter à ce site d'administration.",
verbose_name="statut équipe",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Précise si lutilisateur doit être considéré comme actif. Décochez ceci plutôt que de supprimer le compte.",
verbose_name="actif",
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.Permission",
verbose_name="user permissions",
),
),
],
options={
'verbose_name': 'utilisateur·ice',
'verbose_name_plural': 'utilisateur·ice·s',
"verbose_name": "utilisateur·ice",
"verbose_name_plural": "utilisateur·ice·s",
},
),
]

View file

@ -6,11 +6,11 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
("accounts", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name='User',
name="User",
),
]

View file

@ -1,5 +1,6 @@
from django.urls import include, path
from .views import PasswordChangeView, AccountSettingsView
from .views import AccountSettingsView, PasswordChangeView
app_name = "accounts"

View file

@ -1,15 +1,10 @@
from django.views.generic import TemplateView, RedirectView
from django.views.generic.edit import UpdateView
from django.shortcuts import redirect
from django.urls import reverse
from django.dispatch import receiver
from django.contrib.auth import logout as auth_logout
from django.contrib import messages
from django.contrib.auth import user_logged_in, user_logged_out, user_login_failed
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import PasswordChangeView
from django.contrib import messages
from urllib.parse import quote as urlquote
from django.dispatch import receiver
from django.urls import reverse
from django.views.generic.edit import UpdateView
from .forms import AccountSettingsForm

View file

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

View file

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,10 +1,10 @@
from django.views.generic import TemplateView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.http import Http404
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect, get_object_or_404
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.views.generic import RedirectView, TemplateView
from django.views.generic.detail import SingleObjectMixin
class AddCommentView(LoginRequiredMixin, SingleObjectMixin, RedirectView):

View file

@ -6,6 +6,31 @@
let
nix-pkgs = import sources.nix-pkgs { inherit pkgs; };
check = (import sources.git-hooks).run {
src = ./.;
hooks = {
# Python hooks
ruff.enable = true;
black.enable = true;
isort.enable = true;
# Nix Hooks
statix.enable = true;
deadnix.enable = true;
rfc101 = {
enable = true;
name = "RFC-101 formatting";
entry = "${pkgs.lib.getExe pkgs.nixfmt-rfc-style}";
files = "\\.nix$";
};
# Misc Hooks
commitizen.enable = true;
};
};
python3 = pkgs.python3.override {
packageOverrides = final: _: {
inherit (nix-pkgs)
@ -14,7 +39,7 @@ let
markdown-icons
python-cas
;
authens = final.callPackage ./nix/authens {};
authens = final.callPackage ./nix/authens { };
};
};
in
@ -23,7 +48,7 @@ in
devShell = pkgs.mkShell {
name = "gestiojeux.dev";
packages = [
packages = check.enabledPackages ++ [
(python3.withPackages (ps: [
ps.django
ps.django-types
@ -53,5 +78,8 @@ in
GESTIOJEUX_DEBUG = builtins.toJSON true;
};
shellHook = ''
${check.shellHook}
'';
};
}

View file

@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gestiojeux.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestiojeux.settings")
application = get_asgi_application()

View file

@ -13,10 +13,11 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),

View file

@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gestiojeux.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestiojeux.settings")
application = get_wsgi_application()

View file

@ -1,7 +1,9 @@
from django.contrib import admin
from .models import Category, Tag, Game, GameComment
from comments.admin import CommentAdmin
from .models import Category, Game, GameComment, Tag
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Game)

View file

@ -1,5 +1,6 @@
from loans.forms import BorrowForm
class BorrowGameForm(BorrowForm):
error_css_class = "errorfield"
required_css_class = "requiredfield"

View file

@ -1,6 +1,7 @@
import markdown
from django.template.loader import get_template
from .models import Category, Tag, Game
from .models import Category, Game, Tag
class InventoryLinkProcessor(markdown.inlinepatterns.InlineProcessor):

View file

@ -1,9 +1,10 @@
# Generated by Django 3.1.2 on 2020-12-29 23:21
import autoslug.fields
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import website.validators
@ -17,71 +18,216 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Category',
name="Category",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256, unique=True, verbose_name='nom')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=256, unique=True, verbose_name="nom"),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="name", unique=True
),
),
],
options={
'verbose_name': 'catégorie',
'ordering': ['name'],
"verbose_name": "catégorie",
"ordering": ["name"],
},
),
migrations.CreateModel(
name='Game',
name="Game",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=256, unique=True, verbose_name='titre du jeu')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
('nb_player_min', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s minimum')),
('nb_player_max', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s maximum')),
('player_range', models.CharField(blank=True, help_text='Affichage personnalisé pour le nombre de joueur·se·s', max_length=256, verbose_name='nombre de joueur·se·s')),
('duration', models.CharField(blank=True, max_length=256, verbose_name='durée de partie')),
('game_designer', models.CharField(blank=True, max_length=256, verbose_name='game designer')),
('illustrator', models.CharField(blank=True, max_length=256, verbose_name='illustrateur·trice')),
('editor', models.CharField(blank=True, max_length=256, verbose_name='éditeur')),
('description', models.TextField(blank=True, verbose_name='description')),
('image', models.ImageField(blank=True, help_text="L'image doit peser 512 Kio au maximum", upload_to='game_img/', validators=[website.validators.MaxFileSizeValidator(512)], verbose_name='image')),
('missing_elements', models.TextField(blank=True, verbose_name='pièces manquantes')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, to='inventory.category', verbose_name='catégorie')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"title",
models.CharField(
max_length=256, unique=True, verbose_name="titre du jeu"
),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="title", unique=True
),
),
(
"nb_player_min",
models.PositiveSmallIntegerField(
verbose_name="nombre de joueur·se·s minimum"
),
),
(
"nb_player_max",
models.PositiveSmallIntegerField(
verbose_name="nombre de joueur·se·s maximum"
),
),
(
"player_range",
models.CharField(
blank=True,
help_text="Affichage personnalisé pour le nombre de joueur·se·s",
max_length=256,
verbose_name="nombre de joueur·se·s",
),
),
(
"duration",
models.CharField(
blank=True, max_length=256, verbose_name="durée de partie"
),
),
(
"game_designer",
models.CharField(
blank=True, max_length=256, verbose_name="game designer"
),
),
(
"illustrator",
models.CharField(
blank=True, max_length=256, verbose_name="illustrateur·trice"
),
),
(
"editor",
models.CharField(
blank=True, max_length=256, verbose_name="éditeur"
),
),
(
"description",
models.TextField(blank=True, verbose_name="description"),
),
(
"image",
models.ImageField(
blank=True,
help_text="L'image doit peser 512 Kio au maximum",
upload_to="game_img/",
validators=[website.validators.MaxFileSizeValidator(512)],
verbose_name="image",
),
),
(
"missing_elements",
models.TextField(blank=True, verbose_name="pièces manquantes"),
),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT,
to="inventory.category",
verbose_name="catégorie",
),
),
],
options={
'verbose_name': 'jeu',
'verbose_name_plural': 'jeux',
'ordering': ['title'],
"verbose_name": "jeu",
"verbose_name_plural": "jeux",
"ordering": ["title"],
},
),
migrations.CreateModel(
name='Tag',
name="Tag",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=256, unique=True, verbose_name='nom')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(max_length=256, unique=True, verbose_name="nom"),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="name", unique=True
),
),
],
options={
'ordering': ['name'],
"ordering": ["name"],
},
),
migrations.CreateModel(
name='GameComment',
name="GameComment",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(verbose_name='texte')),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='date de publication')),
('modified_on', models.DateTimeField(auto_now=True, verbose_name='date de modification')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='auteur·ice')),
('commented_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='inventory.game', verbose_name='jeu')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.TextField(verbose_name="texte")),
(
"created_on",
models.DateTimeField(
auto_now_add=True, verbose_name="date de publication"
),
),
(
"modified_on",
models.DateTimeField(
auto_now=True, verbose_name="date de modification"
),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="auteur·ice",
),
),
(
"commented_object",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="inventory.game",
verbose_name="jeu",
),
),
],
options={
'verbose_name': 'commentaire sur un jeu',
'verbose_name_plural': 'commentaires sur des jeux',
'ordering': ['created_on'],
"verbose_name": "commentaire sur un jeu",
"verbose_name_plural": "commentaires sur des jeux",
"ordering": ["created_on"],
},
),
migrations.AddField(
model_name='game',
name='tags',
field=models.ManyToManyField(blank=True, to='inventory.Tag', verbose_name='tags'),
model_name="game",
name="tags",
field=models.ManyToManyField(
blank=True, to="inventory.Tag", verbose_name="tags"
),
),
]

View file

@ -1,32 +1,52 @@
# Generated by Django 4.2.8 on 2024-05-02 09:30
import autoslug.fields
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_duration_range'),
("inventory", "0002_duration_range"),
]
operations = [
migrations.CreateModel(
name='GameLoan',
name="GameLoan",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='lent_object', unique=True)),
('borrow_date', models.DateTimeField(auto_now_add=True)),
('return_date', models.DateTimeField(null=True)),
('mail', models.EmailField(max_length=254)),
('lent_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.game', verbose_name='jeu emprunté')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="lent_object", unique=True
),
),
("borrow_date", models.DateTimeField(auto_now_add=True)),
("return_date", models.DateTimeField(null=True)),
("mail", models.EmailField(max_length=254)),
(
"lent_object",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="inventory.game",
verbose_name="jeu emprunté",
),
),
],
options={
'verbose_name': 'emprunt',
'verbose_name_plural': 'emprunts',
'ordering': ['borrow_date'],
'abstract': False,
"verbose_name": "emprunt",
"verbose_name_plural": "emprunts",
"ordering": ["borrow_date"],
"abstract": False,
},
),
]

View file

@ -6,26 +6,33 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_gameloan'),
("inventory", "0003_gameloan"),
]
operations = [
migrations.AlterModelOptions(
name='category',
options={'ordering': ['name'], 'verbose_name': 'étagère'},
name="category",
options={"ordering": ["name"], "verbose_name": "étagère"},
),
migrations.AlterModelOptions(
name='gameloan',
options={'ordering': ['borrow_date'], 'permissions': [('can_see_loan_details', 'Can see loan details')], 'verbose_name': 'emprunt', 'verbose_name_plural': 'emprunts'},
name="gameloan",
options={
"ordering": ["borrow_date"],
"permissions": [("can_see_loan_details", "Can see loan details")],
"verbose_name": "emprunt",
"verbose_name_plural": "emprunts",
},
),
migrations.AlterField(
model_name='gameloan',
name='borrow_date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Date demprunt'),
model_name="gameloan",
name="borrow_date",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Date demprunt"
),
),
migrations.AlterField(
model_name='gameloan',
name='return_date',
field=models.DateTimeField(null=True, verbose_name='Date de retour'),
model_name="gameloan",
name="return_date",
field=models.DateTimeField(null=True, verbose_name="Date de retour"),
),
]

View file

@ -1,7 +1,7 @@
# Generated by Django 4.2.11 on 2024-07-02 22:59
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View file

@ -1,13 +1,13 @@
from django.db import models
from django.urls import reverse
from django.core.exceptions import ValidationError
import os
import uuid
from autoslug import AutoSlugField
from website.validators import MaxFileSizeValidator
from django.db import models
from django.urls import reverse
from comments.models import AbstractComment
from loans.models import AbstractLoan
from website.validators import MaxFileSizeValidator
class Category(models.Model):
@ -78,7 +78,6 @@ class Game(models.Model):
def __str__(self):
return self.title
def get_player_range(self):
return ""
@ -104,13 +103,12 @@ class GameComment(AbstractComment):
"inventory:modify_game_comment", args=(self.commented_object.slug, self.id)
)
class GameLoan(AbstractLoan):
lent_object = models.ForeignKey(
Game, on_delete=models.CASCADE,
verbose_name="outil emprunté"
Game, on_delete=models.CASCADE, verbose_name="outil emprunté"
)
class Meta(AbstractLoan.Meta):
abstract = False
permissions = [("can_see_loan_details", "Can see loan details")]

View file

@ -1,5 +1,6 @@
from haystack import indexes
from .models import Category, Tag, Game
from .models import Category, Game, Tag
class CategoryIndex(indexes.SearchIndex, indexes.Indexable):

View file

@ -24,7 +24,7 @@ class LoanTable(tables.Table):
def render_slug(self, value, record):
res = ""
if record.return_date == None:
if record.return_date is None:
res = format_html(
"<a class='button' href='{}?next={}'>Rendre l'outil</a>",
reverse("inventory:return_game", args=[value]),

View file

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,10 +1,23 @@
from django.urls import path
from .views import (AddGameCommentView, BorrowGameView, CategoryListView,
CategoryView, DetailLoanView, GameListView, GameLoanView,
GameView, InventorySearchView, InventoryView,
ModifyGameCommentView, OngoingLoansView, QrCodeView,
ReturnGameView, TagListView, TagView)
from .views import (
AddGameCommentView,
BorrowGameView,
CategoryListView,
CategoryView,
DetailLoanView,
GameListView,
GameLoanView,
GameView,
InventorySearchView,
InventoryView,
ModifyGameCommentView,
OngoingLoansView,
QrCodeView,
ReturnGameView,
TagListView,
TagView,
)
app_name = "inventory"

View file

@ -1,5 +1,4 @@
import qrcode
from comments.views import AddCommentView, ModifyCommentView
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import HttpResponse
from django.urls import reverse
@ -8,6 +7,8 @@ from django_tables2.views import SingleTableView
from haystack.forms import SearchForm
from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet
from comments.views import AddCommentView, ModifyCommentView
from loans.views import BorrowView, DetailLoanView, ReturnView
from .forms import BorrowGameForm

View file

@ -1,5 +1,6 @@
from django.contrib import admin
class LoanAdmin(admin.ModelAdmin):
list_display = ("lent_object", "borrow_date", "return_date")
ordering = ("-borrow_date",)

View file

@ -2,5 +2,5 @@ from django.apps import AppConfig
class LoansConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'loans'
default_auto_field = "django.db.models.BigAutoField"
name = "loans"

View file

@ -1,4 +1,5 @@
from django import forms
class BorrowForm(forms.Form):
mail = forms.EmailField(label="Mail")

View file

@ -1,8 +1,8 @@
# Generated by Django 4.2.8 on 2024-04-23 16:45
import autoslug.fields
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
@ -10,24 +10,45 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('inventory', '0002_duration_range'),
("inventory", "0002_duration_range"),
]
operations = [
migrations.CreateModel(
name='Loan',
name="Loan",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='game', unique=True)),
('borrow_date', models.DateTimeField(auto_now_add=True)),
('return_date', models.DateTimeField(null=True)),
('mail', models.EmailField(max_length=254)),
('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='loans', to='inventory.game', verbose_name='jeu emprunté')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="game", unique=True
),
),
("borrow_date", models.DateTimeField(auto_now_add=True)),
("return_date", models.DateTimeField(null=True)),
("mail", models.EmailField(max_length=254)),
(
"game",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="loans",
to="inventory.game",
verbose_name="jeu emprunté",
),
),
],
options={
'verbose_name': 'emprunt',
'verbose_name_plural': 'emprunts',
'ordering': ['borrow_date'],
"verbose_name": "emprunt",
"verbose_name_plural": "emprunts",
"ordering": ["borrow_date"],
},
),
]

View file

@ -6,11 +6,11 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('loans', '0001_initial'),
("loans", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name='Loan',
name="Loan",
),
]

View file

@ -1,12 +1,12 @@
from django.db import models
from autoslug import AutoSlugField
from django.db import models
from django.utils.timezone import now
class AbstractLoan(models.Model):
lent_object = None # Fill this with a foreign key in subclasses
lent_object = None # Fill this with a foreign key in subclasses
slug = AutoSlugField(unique=True, populate_from="lent_object")
borrow_date = models.DateTimeField(
auto_now_add=True, verbose_name="Date demprunt")
borrow_date = models.DateTimeField(auto_now_add=True, verbose_name="Date demprunt")
return_date = models.DateTimeField(null=True, verbose_name="Date de retour")
mail = models.EmailField()
@ -14,7 +14,7 @@ class AbstractLoan(models.Model):
class Meta:
abstract = True
ordering=["borrow_date"]
ordering = ["borrow_date"]
verbose_name = "emprunt"
verbose_name_plural = "emprunts"
@ -26,9 +26,9 @@ class AbstractLoan(models.Model):
self.save()
@classmethod
def ongoing_loans(cls, obj = None):
def ongoing_loans(cls, obj=None):
ongoing = cls.objects.filter(return_date=None)
if obj != None:
if obj is not None:
return ongoing.filter(lent_object=obj)
else:
return ongoing

View file

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,25 +1,25 @@
from django.views.generic import DetailView, FormView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.contrib import messages
from django.shortcuts import redirect
from inventory.models import Game
from .models import AbstractLoan
from django.views.generic import DetailView, FormView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from .forms import BorrowForm
class ReturnView(SingleObjectMixin, RedirectView):
# Inherited classes should contain:
# model = LoanModel
# pattern_name =
# pattern_name =
redirect_slug_field = "slug"
permanent = False
def get_redirect_url(self, *args, **kwargs):
loan = self.get_object()
loan.return_object()
kwargs[self.redirect_slug_field] = getattr(loan.lent_object,
loan.lent_object_slug_field)
kwargs[self.redirect_slug_field] = getattr(
loan.lent_object, loan.lent_object_slug_field
)
messages.success(self.request, "Rendu effectué.")
if "next" in self.request.GET:
return self.request.GET["next"]
@ -31,7 +31,7 @@ class BorrowView(SingleObjectMixin, FormView):
# model = LentObjectModel
# loan_model = LoanModel
# template_name = "path/to/template.html"
form_class = BorrowForm # Update this for a more complex form
form_class = BorrowForm # Update this for a more complex form
def get_initial(self):
initial = super().get_initial()
@ -52,21 +52,22 @@ class BorrowView(SingleObjectMixin, FormView):
loan.save()
self.request.session["loan_mail"] = loan.mail
messages.success(self.request, "Votre emprunt est enregistré.")
return redirect(self.success_pattern_name,
getattr(obj, loan.lent_object_slug_field))
return redirect(
self.success_pattern_name, getattr(obj, loan.lent_object_slug_field)
)
class DetailLoanView(DetailView):
# Inherited classes should contain:
# model = LentObjectModel
# loan_model = LoanModel
# template_name = "path/to/template.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
loans = self.loan_model.ongoing_loans(self.get_object())
is_borrowed = loans.exists()
context["is_borrowed"] = is_borrowed
context["is_borrowed"] = is_borrowed
if is_borrowed:
context["loan"] = loans.get()
return context

View file

@ -5,7 +5,7 @@ import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gestiojeux.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestiojeux.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -17,5 +17,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View file

@ -1,20 +1,34 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
inherit (data) version;
mkSource = spec:
assert spec ? type; let
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then mkGitSource spec
else if spec.type == "GitRelease" then mkGitSource spec
else if spec.type == "PyPi" then mkPyPiSource spec
else if spec.type == "Channel" then mkChannelSource spec
else builtins.throw "Unknown source type ${spec.type}";
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource = { repository, revision, url ? null, hash, ... }:
mkGitSource =
{
repository,
revision,
url ? null,
hash,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
@ -23,19 +37,23 @@ let
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else assert repository.type == "Git"; builtins.fetchGit {
url = repository.url;
rev = revision;
# hash = hash;
};
else
assert repository.type == "Git";
builtins.fetchGit {
inherit (repository) url;
rev = revision;
# hash = hash;
};
mkPyPiSource = { url, hash, ... }:
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource = { url, hash, ... }:
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;

View file

@ -1,5 +1,17 @@
{
"pins": {
"git-hooks": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "cachix",
"repo": "git-hooks.nix"
},
"branch": "master",
"revision": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"url": "https://github.com/cachix/git-hooks.nix/archive/0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07.tar.gz",
"hash": "0bmgc731c5rvky6qxc4f6gvgyiic8dna5dv3j19kya86idf7wn0p"
},
"nix-pkgs": {
"type": "Git",
"repository": {
@ -19,4 +31,4 @@
}
},
"version": 3
}
}

2
pyproject.toml Normal file
View file

@ -0,0 +1,2 @@
[tool.isort]
profile = "black"

View file

@ -1,7 +1,9 @@
from django.contrib import admin
from .models import Suggestion, SuggestionComment
from comments.admin import CommentAdmin
from .models import Suggestion, SuggestionComment
class SuggestionAdmin(admin.ModelAdmin):
exclude = ("upvoting_users",)

View file

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

View file

@ -1,4 +1,5 @@
from django import forms
from .models import Suggestion

View file

@ -1,10 +1,11 @@
# Generated by Django 3.1.2 on 2020-12-29 23:43
import autoslug.fields
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import website.validators
@ -13,52 +14,189 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('inventory', '0001_initial'),
("inventory", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Suggestion',
name="Suggestion",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=256, unique=True, verbose_name='titre du jeu')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
('price', models.DecimalField(decimal_places=2, max_digits=6, validators=[django.core.validators.MinValueValidator(0)], verbose_name='prix en euros')),
('buy_link', models.URLField(verbose_name="lien vers un site d'achat")),
('nb_player_min', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s minimum')),
('nb_player_max', models.PositiveSmallIntegerField(verbose_name='nombre de joueur·se·s maximum')),
('player_range_precisions', models.CharField(blank=True, help_text='Pour indiquer une éventuelle contrainte (ex. parité) ou information sur le nombre de joueur·se·s', max_length=256, verbose_name='précisions sur le nombre de joueur·se·s')),
('duration', models.CharField(max_length=256, verbose_name='durée de partie')),
('game_designer', models.CharField(blank=True, max_length=256, verbose_name='game designer')),
('illustrator', models.CharField(blank=True, max_length=256, verbose_name='illustrateur·trice')),
('editor', models.CharField(blank=True, max_length=256, verbose_name='éditeur')),
('description', models.TextField(blank=True, help_text="Peut correspondre à celle de l'éditeur et ne doit pas contenir d'avis personnel", verbose_name='description')),
('image', models.ImageField(blank=True, help_text='Image du jeu de moins de 512 Kio à téléverser (par exemple une photo de sa boite)', upload_to='suggestion_img/', validators=[website.validators.MaxFileSizeValidator(512)], verbose_name='image')),
('category', models.ForeignKey(blank=True, help_text='Idée de catégorie dans laquelle ranger ce jeu', null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.category', verbose_name='catégorie')),
('tags', models.ManyToManyField(blank=True, help_text="Vous pouvez en sélectionner plusieurs ou aucun (sur ordinateur Ctrl+Clic change l'état de selection d'un tag)", to='inventory.Tag', verbose_name='tags qui correspondent à ce jeu')),
('upvoting_users', models.ManyToManyField(blank=True, related_name='upvoted_suggestions', to=settings.AUTH_USER_MODEL, verbose_name='personnes intéressées')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"title",
models.CharField(
max_length=256, unique=True, verbose_name="titre du jeu"
),
),
(
"slug",
autoslug.fields.AutoSlugField(
editable=False, populate_from="title", unique=True
),
),
(
"price",
models.DecimalField(
decimal_places=2,
max_digits=6,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="prix en euros",
),
),
("buy_link", models.URLField(verbose_name="lien vers un site d'achat")),
(
"nb_player_min",
models.PositiveSmallIntegerField(
verbose_name="nombre de joueur·se·s minimum"
),
),
(
"nb_player_max",
models.PositiveSmallIntegerField(
verbose_name="nombre de joueur·se·s maximum"
),
),
(
"player_range_precisions",
models.CharField(
blank=True,
help_text="Pour indiquer une éventuelle contrainte (ex. parité) ou information sur le nombre de joueur·se·s",
max_length=256,
verbose_name="précisions sur le nombre de joueur·se·s",
),
),
(
"duration",
models.CharField(max_length=256, verbose_name="durée de partie"),
),
(
"game_designer",
models.CharField(
blank=True, max_length=256, verbose_name="game designer"
),
),
(
"illustrator",
models.CharField(
blank=True, max_length=256, verbose_name="illustrateur·trice"
),
),
(
"editor",
models.CharField(
blank=True, max_length=256, verbose_name="éditeur"
),
),
(
"description",
models.TextField(
blank=True,
help_text="Peut correspondre à celle de l'éditeur et ne doit pas contenir d'avis personnel",
verbose_name="description",
),
),
(
"image",
models.ImageField(
blank=True,
help_text="Image du jeu de moins de 512 Kio à téléverser (par exemple une photo de sa boite)",
upload_to="suggestion_img/",
validators=[website.validators.MaxFileSizeValidator(512)],
verbose_name="image",
),
),
(
"category",
models.ForeignKey(
blank=True,
help_text="Idée de catégorie dans laquelle ranger ce jeu",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="inventory.category",
verbose_name="catégorie",
),
),
(
"tags",
models.ManyToManyField(
blank=True,
help_text="Vous pouvez en sélectionner plusieurs ou aucun (sur ordinateur Ctrl+Clic change l'état de selection d'un tag)",
to="inventory.Tag",
verbose_name="tags qui correspondent à ce jeu",
),
),
(
"upvoting_users",
models.ManyToManyField(
blank=True,
related_name="upvoted_suggestions",
to=settings.AUTH_USER_MODEL,
verbose_name="personnes intéressées",
),
),
],
options={
'verbose_name': 'suggestion de jeu',
'verbose_name_plural': 'suggestions de jeux',
'ordering': ['title'],
"verbose_name": "suggestion de jeu",
"verbose_name_plural": "suggestions de jeux",
"ordering": ["title"],
},
),
migrations.CreateModel(
name='SuggestionComment',
name="SuggestionComment",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(verbose_name='texte')),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='date de publication')),
('modified_on', models.DateTimeField(auto_now=True, verbose_name='date de modification')),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='auteur·ice')),
('commented_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='suggestions.suggestion', verbose_name='suggestion')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("text", models.TextField(verbose_name="texte")),
(
"created_on",
models.DateTimeField(
auto_now_add=True, verbose_name="date de publication"
),
),
(
"modified_on",
models.DateTimeField(
auto_now=True, verbose_name="date de modification"
),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="auteur·ice",
),
),
(
"commented_object",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="suggestions.suggestion",
verbose_name="suggestion",
),
),
],
options={
'verbose_name': 'commentaire sur une suggestion',
'verbose_name_plural': 'commentaires sur des suggestions',
'ordering': ['created_on'],
"verbose_name": "commentaire sur une suggestion",
"verbose_name_plural": "commentaires sur des suggestions",
"ordering": ["created_on"],
},
),
]

View file

@ -6,29 +6,43 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('suggestions', '0001_initial'),
("suggestions", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name='suggestion',
name='duration',
model_name="suggestion",
name="duration",
),
migrations.AddField(
model_name='suggestion',
name='duration_max',
field=models.PositiveSmallIntegerField(blank=True, default=60, help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide", verbose_name='durée de partie maximale'),
model_name="suggestion",
name="duration_max",
field=models.PositiveSmallIntegerField(
blank=True,
default=60,
help_text="En minutes, telle qu'indiquée par l'éditeur, identique à la durée minimale si laissée vide",
verbose_name="durée de partie maximale",
),
preserve_default=False,
),
migrations.AddField(
model_name='suggestion',
name='duration_min',
field=models.PositiveSmallIntegerField(default=60, help_text="En minutes, telle qu'indiquée par l'éditeur", verbose_name='durée de partie minimale'),
model_name="suggestion",
name="duration_min",
field=models.PositiveSmallIntegerField(
default=60,
help_text="En minutes, telle qu'indiquée par l'éditeur",
verbose_name="durée de partie minimale",
),
preserve_default=False,
),
migrations.AddField(
model_name='suggestion',
name='duration_precisions',
field=models.CharField(blank=True, help_text='Pour indiquer des informations complémentaires sur la durée de la partie (ex. évolution en fonction du nombre de joueur·se·s)', max_length=256, verbose_name='précisions sur la durée de partie'),
model_name="suggestion",
name="duration_precisions",
field=models.CharField(
blank=True,
help_text="Pour indiquer des informations complémentaires sur la durée de la partie (ex. évolution en fonction du nombre de joueur·se·s)",
max_length=256,
verbose_name="précisions sur la durée de partie",
),
),
]

View file

@ -1,12 +1,13 @@
from autoslug import AutoSlugField
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.urls import reverse
from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
from autoslug import AutoSlugField
from website.validators import MaxFileSizeValidator
from django.contrib.auth.models import User
from inventory.models import Category, Tag
from comments.models import AbstractComment
from inventory.models import Category, Tag
from website.validators import MaxFileSizeValidator
class Suggestion(models.Model):

View file

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,12 +1,13 @@
from django.urls import path
from .views import (
SuggestionListView,
AddSuggestionCommentView,
AddSuggestionView,
DownvoteSuggestionView,
ModifySuggestionCommentView,
SuggestionListView,
SuggestionView,
UpvoteSuggestionView,
DownvoteSuggestionView,
AddSuggestionCommentView,
ModifySuggestionCommentView,
)
app_name = "suggestions"

View file

@ -1,12 +1,14 @@
from django.views.generic import ListView, DetailView, FormView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count
from django.shortcuts import redirect
from django.views.generic import DetailView, FormView, ListView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from comments.views import AddCommentView, ModifyCommentView
from .models import Suggestion, SuggestionComment
from .forms import AddSuggestionForm
from .models import Suggestion, SuggestionComment
class SuggestionListView(ListView):

View file

@ -1,5 +1,6 @@
from django.contrib import admin
from .models import MarkdownPage
from markdownx.admin import MarkdownxModelAdmin
from .models import MarkdownPage
admin.site.register(MarkdownPage, MarkdownxModelAdmin)

View file

@ -1,6 +1,7 @@
import markdown
import re
import markdown
class NbspPreprocessor(markdown.preprocessors.Preprocessor):
"""Replace regular spaces with non-breaking spaces within a text around relevant

View file

@ -1,28 +1,43 @@
# Generated by Django 3.1.2 on 2020-12-27 11:34
from django.db import migrations, models
import markdownx.models
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='MarkdownPage',
name="MarkdownPage",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, help_text="Identifiant de la page qui se voit dans l'URL. Ne doit pas collisionner avec une page existante. Laisser vide pour la page d'accueil, requis sinon.", unique=True, verbose_name='Adresse de la page')),
('content', markdownx.models.MarkdownxField(verbose_name='Contenu')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"slug",
models.SlugField(
blank=True,
help_text="Identifiant de la page qui se voit dans l'URL. Ne doit pas collisionner avec une page existante. Laisser vide pour la page d'accueil, requis sinon.",
unique=True,
verbose_name="Adresse de la page",
),
),
("content", markdownx.models.MarkdownxField(verbose_name="Contenu")),
],
options={
'verbose_name': 'page Markdown',
'verbose_name_plural': 'pages Markdown',
'ordering': ['slug'],
"verbose_name": "page Markdown",
"verbose_name_plural": "pages Markdown",
"ordering": ["slug"],
},
),
]

View file

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,4 +1,5 @@
from django.urls import path
from .views import MarkdownPageView
app_name = "website"

View file

@ -1,6 +1,7 @@
from django.views.generic import DetailView
from django.utils.safestring import mark_safe
from django.views.generic import DetailView
from markdownx.utils import markdownify
from .models import MarkdownPage