diff --git a/la_chariotte/asgi.py b/la_chariotte/asgi.py index cbce385..5baa79f 100644 --- a/la_chariotte/asgi.py +++ b/la_chariotte/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'la_chariotte.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "la_chariotte.settings") application = get_asgi_application() diff --git a/la_chariotte/order/apps.py b/la_chariotte/order/apps.py index 4cd66cc..337993a 100644 --- a/la_chariotte/order/apps.py +++ b/la_chariotte/order/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class OrderConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'la_chariotte.order' + default_auto_field = "django.db.models.BigAutoField" + name = "la_chariotte.order" diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index ef55d2e..4088d2f 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -4,37 +4,56 @@ from django.db import models from django.utils import timezone + class Grouped_order(models.Model): name = models.CharField(max_length=100, null=True) # optionnal - orga = models.CharField(max_length=100) # a changer, utiliser ForeignKey de user - delivery_date = models.DateField('Date de livraison') - deadline = models.DateTimeField('Date limite de commande') + orga = models.CharField(max_length=100) # a changer, utiliser ForeignKey de user + delivery_date = models.DateField("Date de livraison") + deadline = models.DateTimeField("Date limite de commande") def is_ongoing(self): """Returns True if the grouped order is open for new Orders - False if it's too late""" return self.deadline >= timezone.now() def __str__(self): - return self.name if self.name else f"Commande groupée {self.pk} du {self.date} organisée par {self.orga}" + return ( + self.name + if self.name + else f"Commande groupée {self.pk} du {self.date} organisée par {self.orga}" + ) + class Order(models.Model): - grouped_order = models.ForeignKey(Grouped_order, on_delete=models.CASCADE,related_name="order_set") - author = models.CharField(max_length=100, verbose_name='Personne qui passe la commande') # a changer, utiliser ForeignKey de user + grouped_order = models.ForeignKey( + Grouped_order, on_delete=models.CASCADE, related_name="order_set" + ) + author = models.CharField( + max_length=100, verbose_name="Personne qui passe la commande" + ) # a changer, utiliser ForeignKey de user + def __str__(self): return f"Commande de {self.author} pour la commande groupée {self.grouped_order.pk}" + class Item(models.Model): name = models.CharField(max_length=100) - grouped_order = models.ForeignKey(Grouped_order, on_delete=models.CASCADE) # à transformer en manytomany quand il y aura un catalogue + grouped_order = models.ForeignKey( + Grouped_order, on_delete=models.CASCADE + ) # à transformer en manytomany quand il y aura un catalogue ordered_nb = models.IntegerField(default=0) def __str__(self): return f"{self.name} dans la commande groupée {self.grouped_order.pk}" + class OrderedItem(models.Model): """Item in one specific Order, and its number""" - nb = models.PositiveSmallIntegerField(default=0) # works up to 32767 - order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="ordered_items") + + nb = models.PositiveSmallIntegerField(default=0) # works up to 32767 + order = models.ForeignKey( + Order, on_delete=models.CASCADE, related_name="ordered_items" + ) item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="orders") + def __str__(self): return f"{self.nb} {self.item}, dans la commande {self.order.pk}" diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py index 2977c49..b36d2e3 100644 --- a/la_chariotte/order/tests/test_models.py +++ b/la_chariotte/order/tests/test_models.py @@ -4,9 +4,9 @@ from django.utils import timezone from la_chariotte.order.models import Grouped_order - -class TestGroupedOrdersModel(): +class TestGroupedOrdersModel: """Tests for Grouped orders model""" + def test_is_ongoing_with_ongoing_grouped_order(self): """ is_ongoing() returns True if the deadline is not crossed @@ -21,4 +21,4 @@ class TestGroupedOrdersModel(): """ deadline = timezone.now() - datetime.timedelta(hours=1) ongoing_gr_order = Grouped_order(deadline=deadline) - assert not ongoing_gr_order.is_ongoing() \ No newline at end of file + assert not ongoing_gr_order.is_ongoing() diff --git a/la_chariotte/order/tests/test_views.py b/la_chariotte/order/tests/test_views.py index 1074ae3..b3d9364 100644 --- a/la_chariotte/order/tests/test_views.py +++ b/la_chariotte/order/tests/test_views.py @@ -8,74 +8,125 @@ from la_chariotte.order.models import Grouped_order pytestmark = pytest.mark.django_db -def create_grouped_order(days_before_delivery_date,days_before_deadline,name): + +def create_grouped_order(days_before_delivery_date, days_before_deadline, name): """ Creates a grouped order. """ date = timezone.now().date() + datetime.timedelta(days=days_before_delivery_date) deadline = timezone.now() + datetime.timedelta(days=days_before_deadline) - return Grouped_order.objects.create(name=name, orga="test orga",delivery_date=date,deadline=deadline) + return Grouped_order.objects.create( + name=name, orga="test orga", delivery_date=date, deadline=deadline + ) + class TestGroupedOrderIndexView: - def test_no_grouped_orders(self,client): + def test_no_grouped_orders(self, client): """ If no grouped order exist, an appropriate message is displayed """ - response = client.get(reverse('order:index')) + response = client.get(reverse("order:index")) assert response.status_code == 200 assert "Pas de commande groupée pour l'instant" in response.content.decode() - assert len(response.context['grouped_order_list']['old_grouped_orders']) == 0 - assert len(response.context['grouped_order_list']['orders_over_grouped_orders']) == 0 - assert len(response.context['grouped_order_list']['incoming_grouped_orders']) == 0 + assert len(response.context["grouped_order_list"]["old_grouped_orders"]) == 0 + assert ( + len(response.context["grouped_order_list"]["orders_over_grouped_orders"]) + == 0 + ) + assert ( + len(response.context["grouped_order_list"]["incoming_grouped_orders"]) == 0 + ) - def test_grouped_orders_in_right_section(self,client): + def test_grouped_orders_in_right_section(self, client): """ According to their delivery date and deadline, grouped orders are placed in the correct section : several gr orders """ - future_grouped_order = create_grouped_order(days_before_delivery_date=5,days_before_deadline=2,name="future") - crossed_deadline_gr_order = create_grouped_order(days_before_delivery_date=2, days_before_deadline=-1,name="crossed deadline") - old_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="old") - response = client.get(reverse('order:index')) + future_grouped_order = create_grouped_order( + days_before_delivery_date=5, days_before_deadline=2, name="future" + ) + crossed_deadline_gr_order = create_grouped_order( + days_before_delivery_date=2, + days_before_deadline=-1, + name="crossed deadline", + ) + old_gr_order = create_grouped_order( + days_before_delivery_date=-1, days_before_deadline=-3, name="old" + ) + response = client.get(reverse("order:index")) assert response.status_code == 200 assert "Pas de commande groupée pour l'instant" not in response.content.decode() assert "Commandes groupées à venir" in response.content.decode() assert "Livraison à venir" in response.content.decode() assert "Livraison passée" in response.content.decode() - assert len(response.context['grouped_order_list']['old_grouped_orders']) == 1 - assert len(response.context['grouped_order_list']['orders_over_grouped_orders']) == 1 - assert len(response.context['grouped_order_list']['incoming_grouped_orders']) == 1 - assert response.context['grouped_order_list']['old_grouped_orders'][0] == old_gr_order - assert response.context['grouped_order_list']['orders_over_grouped_orders'][0] == crossed_deadline_gr_order - assert response.context['grouped_order_list']['incoming_grouped_orders'][0] == future_grouped_order + assert len(response.context["grouped_order_list"]["old_grouped_orders"]) == 1 + assert ( + len(response.context["grouped_order_list"]["orders_over_grouped_orders"]) + == 1 + ) + assert ( + len(response.context["grouped_order_list"]["incoming_grouped_orders"]) == 1 + ) + assert ( + response.context["grouped_order_list"]["old_grouped_orders"][0] + == old_gr_order + ) + assert ( + response.context["grouped_order_list"]["orders_over_grouped_orders"][0] + == crossed_deadline_gr_order + ) + assert ( + response.context["grouped_order_list"]["incoming_grouped_orders"][0] + == future_grouped_order + ) - def test_grouped_orders_in_right_section__with_only_old(self,client): + def test_grouped_orders_in_right_section__with_only_old(self, client): """ According to their delivery date and deadline, grouped orders are placed in correct section : only old gr order """ - old_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="passée") - response = client.get(reverse('order:index')) + old_gr_order = create_grouped_order( + days_before_delivery_date=-1, days_before_deadline=-3, name="passée" + ) + response = client.get(reverse("order:index")) assert response.status_code == 200 assert "Pas de commande groupée pour l'instant" not in response.content.decode() assert "Commandes groupées à venir" not in response.content.decode() assert "Livraison à venir" not in response.content.decode() assert "Livraison passée" in response.content.decode() - assert len(response.context['grouped_order_list']['old_grouped_orders']) == 1 - assert len(response.context['grouped_order_list']['orders_over_grouped_orders']) == 0 - assert len(response.context['grouped_order_list']['incoming_grouped_orders']) == 0 - assert response.context['grouped_order_list']['old_grouped_orders'][0] == old_gr_order + assert len(response.context["grouped_order_list"]["old_grouped_orders"]) == 1 + assert ( + len(response.context["grouped_order_list"]["orders_over_grouped_orders"]) + == 0 + ) + assert ( + len(response.context["grouped_order_list"]["incoming_grouped_orders"]) == 0 + ) + assert ( + response.context["grouped_order_list"]["old_grouped_orders"][0] + == old_gr_order + ) - def test_grouped_orders_in_right_section__with_only_future(self,client): + def test_grouped_orders_in_right_section__with_only_future(self, client): """ According to their delivery date and deadline, grouped orders are placed in correct section : only incoming gr order """ - future_grouped_order = create_grouped_order(days_before_delivery_date=5,days_before_deadline=2,name="future") - response = client.get(reverse('order:index')) + future_grouped_order = create_grouped_order( + days_before_delivery_date=5, days_before_deadline=2, name="future" + ) + response = client.get(reverse("order:index")) assert response.status_code == 200 assert "Pas de commande groupée pour l'instant" not in response.content.decode() assert "Commandes groupées à venir" in response.content.decode() assert "Livraison à venir" not in response.content.decode() assert "Livraison passée" not in response.content.decode() - assert len(response.context['grouped_order_list']['old_grouped_orders']) == 0 - assert len(response.context['grouped_order_list']['orders_over_grouped_orders']) == 0 - assert len(response.context['grouped_order_list']['incoming_grouped_orders']) == 1 - assert response.context['grouped_order_list']['incoming_grouped_orders'][0] == future_grouped_order \ No newline at end of file + assert len(response.context["grouped_order_list"]["old_grouped_orders"]) == 0 + assert ( + len(response.context["grouped_order_list"]["orders_over_grouped_orders"]) + == 0 + ) + assert ( + len(response.context["grouped_order_list"]["incoming_grouped_orders"]) == 1 + ) + assert ( + response.context["grouped_order_list"]["incoming_grouped_orders"][0] + == future_grouped_order + ) diff --git a/la_chariotte/order/urls.py b/la_chariotte/order/urls.py index 74c6383..a769aa3 100644 --- a/la_chariotte/order/urls.py +++ b/la_chariotte/order/urls.py @@ -2,10 +2,14 @@ from django.urls import path from . import views -app_name = 'order' +app_name = "order" urlpatterns = [ - path('', views.IndexView.as_view(), name='index'), - path('/', views.GroupedOrderDetailView.as_view(), name='grouped_order_detail'), - path('/orga', views.GroupedOrderOrgaView.as_view(), name='grouped_order_orga'), - path('/commander/', views.order, name='order'), -] \ No newline at end of file + path("", views.IndexView.as_view(), name="index"), + path( + "/", views.GroupedOrderDetailView.as_view(), name="grouped_order_detail" + ), + path( + "/orga", views.GroupedOrderOrgaView.as_view(), name="grouped_order_orga" + ), + path("/commander/", views.order, name="order"), +] diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 72ce0ec..8a969c0 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -9,49 +9,77 @@ from .models import Grouped_order, Item, Order, OrderedItem class IndexView(generic.ListView): """Vue de toutes les commandes groupées existantes - plus tard, de toutes les commandes groupées de l'utilisateur connecté""" - template_name = 'order/index.html' - context_object_name = 'grouped_order_list' + + template_name = "order/index.html" + context_object_name = "grouped_order_list" def get_queryset(self): """Return the 5 most recent old grouped orders""" - old_grouped_orders = Grouped_order.objects.filter(delivery_date__lt=timezone.now().date()).order_by("-delivery_date")[:5] # delivery date < today (delivered) + old_grouped_orders = Grouped_order.objects.filter( + delivery_date__lt=timezone.now().date() + ).order_by("-delivery_date")[ + :5 + ] # delivery date < today (delivered) """Return all grouped orders, for which we cannot order anymore but the delivery date is still to come""" - orders_over_grouped_orders = Grouped_order.objects.filter(delivery_date__gte=timezone.now().date()).filter(deadline__lt=timezone.now()).order_by("-delivery_date") # delivery date >= today (not delivered) and deadline < today (we cannot order) + orders_over_grouped_orders = ( + Grouped_order.objects.filter(delivery_date__gte=timezone.now().date()) + .filter(deadline__lt=timezone.now()) + .order_by("-delivery_date") + ) # delivery date >= today (not delivered) and deadline < today (we cannot order) """Return all incoming grouped orders""" - incoming_grouped_orders = Grouped_order.objects.filter(deadline__gte=timezone.now()).order_by("deadline") # dealine >= today (we can still order) - return {'old_grouped_orders': old_grouped_orders,'orders_over_grouped_orders': orders_over_grouped_orders,'incoming_grouped_orders': incoming_grouped_orders} + incoming_grouped_orders = Grouped_order.objects.filter( + deadline__gte=timezone.now() + ).order_by( + "deadline" + ) # dealine >= today (we can still order) + return { + "old_grouped_orders": old_grouped_orders, + "orders_over_grouped_orders": orders_over_grouped_orders, + "incoming_grouped_orders": incoming_grouped_orders, + } class GroupedOrderDetailView(generic.DetailView): """Vue de détail d'une commande groupée - possibilité de commander si elle est en cours""" + model = Grouped_order - template_name = 'order/grouped_order_detail.html' + template_name = "order/grouped_order_detail.html" class GroupedOrderOrgaView(generic.DetailView): """Vue de supervision d'une commande groupée""" + model = Grouped_order - template_name = 'order/grouped_order_orga.html' + template_name = "order/grouped_order_orga.html" -def order(request, grouped_order_id): # crée une commande (order) pour cette commande groupée, avec l'item selectionné dedans +def order( + request, grouped_order_id +): # crée une commande (order) pour cette commande groupée, avec l'item selectionné dedans grouped_order = get_object_or_404(Grouped_order, pk=grouped_order_id) try: - selected_item = grouped_order.item_set.get(pk=request.POST['item']) + selected_item = grouped_order.item_set.get(pk=request.POST["item"]) except (KeyError, Item.DoesNotExist): # Redisplay the order form for this grouped order. - return render(request, 'order/grouped_order_detail.html', { - 'grouped_order': grouped_order, - 'error_message': "You didn't select an item.", - }) + return render( + request, + "order/grouped_order_detail.html", + { + "grouped_order": grouped_order, + "error_message": "You didn't select an item.", + }, + ) else: - order = Order.objects.create(author="Auteur teur",grouped_order=grouped_order) - OrderedItem.objects.create(nb=1,order=order,item=selected_item) + order = Order.objects.create(author="Auteur teur", grouped_order=grouped_order) + OrderedItem.objects.create(nb=1, order=order, item=selected_item) compute_ordered_nb(selected_item) # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. - return HttpResponseRedirect(reverse('order:grouped_order_orga', args=(grouped_order.pk,))) + return HttpResponseRedirect( + reverse("order:grouped_order_orga", args=(grouped_order.pk,)) + ) + def compute_ordered_nb(item): """Calcule le nombre de produits de ce produit commandés (pour cette commande groupée)""" diff --git a/la_chariotte/settings.py b/la_chariotte/settings.py index fe657cb..9de6adb 100644 --- a/la_chariotte/settings.py +++ b/la_chariotte/settings.py @@ -20,7 +20,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-f66vu+dj79c(5u(w2i0indkrlf$qtt!b$dmotnm%5!0a*9+=my' +SECRET_KEY = "django-insecure-f66vu+dj79c(5u(w2i0indkrlf$qtt!b$dmotnm%5!0a*9+=my" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,57 +31,57 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'la_chariotte.order', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', + "la_chariotte.order", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'la_chariotte.urls' +ROOT_URLCONF = "la_chariotte.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'la_chariotte.wsgi.application' +WSGI_APPLICATION = "la_chariotte.wsgi.application" # Database # https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'chariotte-db', - 'USER': 'laetitia', - 'PASSWORD': 'toto', - 'HOST': '127.0.0.1', - 'PORT': '5432', + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "chariotte-db", + "USER": "laetitia", + "PASSWORD": "toto", + "HOST": "127.0.0.1", + "PORT": "5432", } } @@ -91,16 +91,16 @@ DATABASES = { 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", }, ] @@ -108,9 +108,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/4.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'Europe/Paris' +TIME_ZONE = "Europe/Paris" USE_I18N = True @@ -120,9 +120,9 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.1/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/la_chariotte/urls.py b/la_chariotte/urls.py index ebc0a7d..6bea7b4 100644 --- a/la_chariotte/urls.py +++ b/la_chariotte/urls.py @@ -14,9 +14,9 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path,include +from django.urls import path, include urlpatterns = [ - path('admin/', admin.site.urls), - path('commande/', include('la_chariotte.order.urls')), + path("admin/", admin.site.urls), + path("commande/", include("la_chariotte.order.urls")), ] diff --git a/la_chariotte/wsgi.py b/la_chariotte/wsgi.py index e4e5d8a..0e3a6e0 100644 --- a/la_chariotte/wsgi.py +++ b/la_chariotte/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'la_chariotte.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "la_chariotte.settings") application = get_wsgi_application() diff --git a/manage.py b/manage.py index 0f7ee05..23c217b 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'la_chariotte.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "la_chariotte.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml index d9b0533..b165014 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,4 +3,16 @@ name = "la chariotte" version = "0.0.1" description = "Web application for organising grouped orders" readme = "readMe.md" -license = {file = "LICENSE"} \ No newline at end of file +license = {file = "LICENSE"} + +[tool.black] +line-length = 88 +exclude = ''' + +( + /( + | migrations + | static + )/ +) +''' diff --git a/readMe.md b/readMe.md index 910b1a4..574c3ec 100644 --- a/readMe.md +++ b/readMe.md @@ -9,3 +9,9 @@ Avec pytest, ```bash pytest ``` + +Formater les fichiers avec black : + +```bash +black . +```