From 1c4ea1a3faea13cd88e0d70450a161a1a2e5b3c0 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Fri, 17 Mar 2023 16:04:09 +0100 Subject: [PATCH 01/18] =?UTF-8?q?possibilit=C3=A9=20de=20commander=201=20p?= =?UTF-8?q?roduit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- la_chariotte/order/__init__.py | 0 la_chariotte/order/admin.py | 7 + la_chariotte/order/apps.py | 6 + la_chariotte/order/migrations/0001_initial.py | 31 ++++ .../migrations/0002_grouped_order_name.py | 18 ++ la_chariotte/order/migrations/0003_item.py | 22 +++ .../order/migrations/0004_item_ordered_nb.py | 18 ++ ...d_nb_alter_order_grouped_order_and_more.py | 32 ++++ .../order/migrations/0006_item_ordered_nb.py | 18 ++ la_chariotte/order/migrations/__init__.py | 0 la_chariotte/order/models.py | 28 +++ .../templates/order/grouped_order_detail.html | 36 ++++ .../templates/order/grouped_order_orga.html | 34 ++++ la_chariotte/order/templates/order/index.html | 25 +++ la_chariotte/order/tests.py | 3 + la_chariotte/order/urls.py | 11 ++ la_chariotte/order/views.py | 56 ++++++ la_chariotte/readMe.md | 166 ++++++++++++++++++ la_chariotte/settings.py | 11 +- la_chariotte/urls.py | 3 +- 20 files changed, 521 insertions(+), 4 deletions(-) create mode 100644 la_chariotte/order/__init__.py create mode 100644 la_chariotte/order/admin.py create mode 100644 la_chariotte/order/apps.py create mode 100644 la_chariotte/order/migrations/0001_initial.py create mode 100644 la_chariotte/order/migrations/0002_grouped_order_name.py create mode 100644 la_chariotte/order/migrations/0003_item.py create mode 100644 la_chariotte/order/migrations/0004_item_ordered_nb.py create mode 100644 la_chariotte/order/migrations/0005_remove_item_ordered_nb_alter_order_grouped_order_and_more.py create mode 100644 la_chariotte/order/migrations/0006_item_ordered_nb.py create mode 100644 la_chariotte/order/migrations/__init__.py create mode 100644 la_chariotte/order/models.py create mode 100644 la_chariotte/order/templates/order/grouped_order_detail.html create mode 100644 la_chariotte/order/templates/order/grouped_order_orga.html create mode 100644 la_chariotte/order/templates/order/index.html create mode 100644 la_chariotte/order/tests.py create mode 100644 la_chariotte/order/urls.py create mode 100644 la_chariotte/order/views.py create mode 100644 la_chariotte/readMe.md diff --git a/la_chariotte/order/__init__.py b/la_chariotte/order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/la_chariotte/order/admin.py b/la_chariotte/order/admin.py new file mode 100644 index 0000000..817ee91 --- /dev/null +++ b/la_chariotte/order/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from .models import Grouped_order, Order, Item + +admin.site.register(Grouped_order) +admin.site.register(Order) +admin.site.register(Item) diff --git a/la_chariotte/order/apps.py b/la_chariotte/order/apps.py new file mode 100644 index 0000000..4cd66cc --- /dev/null +++ b/la_chariotte/order/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OrderConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'la_chariotte.order' diff --git a/la_chariotte/order/migrations/0001_initial.py b/la_chariotte/order/migrations/0001_initial.py new file mode 100644 index 0000000..d72f72e --- /dev/null +++ b/la_chariotte/order/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.7 on 2023-03-14 15:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Grouped_order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orga', models.CharField(max_length=100)), + ('date', models.DateField(verbose_name='Date de livraison')), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('author', models.CharField(max_length=100, verbose_name='Personne qui passe la commande')), + ('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')), + ], + ), + ] diff --git a/la_chariotte/order/migrations/0002_grouped_order_name.py b/la_chariotte/order/migrations/0002_grouped_order_name.py new file mode 100644 index 0000000..87ff618 --- /dev/null +++ b/la_chariotte/order/migrations/0002_grouped_order_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-14 16:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='grouped_order', + name='name', + field=models.CharField(max_length=100, null=True), + ), + ] diff --git a/la_chariotte/order/migrations/0003_item.py b/la_chariotte/order/migrations/0003_item.py new file mode 100644 index 0000000..3de7cad --- /dev/null +++ b/la_chariotte/order/migrations/0003_item.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.7 on 2023-03-15 15:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0002_grouped_order_name'), + ] + + operations = [ + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')), + ], + ), + ] diff --git a/la_chariotte/order/migrations/0004_item_ordered_nb.py b/la_chariotte/order/migrations/0004_item_ordered_nb.py new file mode 100644 index 0000000..382f464 --- /dev/null +++ b/la_chariotte/order/migrations/0004_item_ordered_nb.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-15 16:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0003_item'), + ] + + operations = [ + migrations.AddField( + model_name='item', + name='ordered_nb', + field=models.IntegerField(default=0), + ), + ] diff --git a/la_chariotte/order/migrations/0005_remove_item_ordered_nb_alter_order_grouped_order_and_more.py b/la_chariotte/order/migrations/0005_remove_item_ordered_nb_alter_order_grouped_order_and_more.py new file mode 100644 index 0000000..70d001d --- /dev/null +++ b/la_chariotte/order/migrations/0005_remove_item_ordered_nb_alter_order_grouped_order_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.7 on 2023-03-17 14:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0004_item_ordered_nb'), + ] + + operations = [ + migrations.RemoveField( + model_name='item', + name='ordered_nb', + ), + migrations.AlterField( + model_name='order', + name='grouped_order', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_set', to='order.grouped_order'), + ), + migrations.CreateModel( + name='OrderedItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nb', models.PositiveSmallIntegerField(default=0)), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ordered_items', to='order.item')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='order.order')), + ], + ), + ] diff --git a/la_chariotte/order/migrations/0006_item_ordered_nb.py b/la_chariotte/order/migrations/0006_item_ordered_nb.py new file mode 100644 index 0000000..6989454 --- /dev/null +++ b/la_chariotte/order/migrations/0006_item_ordered_nb.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-17 14:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0005_remove_item_ordered_nb_alter_order_grouped_order_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='item', + name='ordered_nb', + field=models.IntegerField(default=0), + ), + ] diff --git a/la_chariotte/order/migrations/__init__.py b/la_chariotte/order/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py new file mode 100644 index 0000000..7714ad6 --- /dev/null +++ b/la_chariotte/order/models.py @@ -0,0 +1,28 @@ +from django.db import models + +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 + date = models.DateField('Date de livraison') + def __str__(self): + 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 + 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 + 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") + item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="orders") diff --git a/la_chariotte/order/templates/order/grouped_order_detail.html b/la_chariotte/order/templates/order/grouped_order_detail.html new file mode 100644 index 0000000..db14d26 --- /dev/null +++ b/la_chariotte/order/templates/order/grouped_order_detail.html @@ -0,0 +1,36 @@ + + + + + Commandes groupées {{ grouped_order.id }} + + +

Détail de la commande groupée {{ grouped_order.id }}, avec la liste des produits associés

+
+

{{ grouped_order }} +

Organisateur·ice : {{ grouped_order.orga }}

+

Date de livraison : {{ grouped_order.date }}

+ + les produits disponibles pour cette commande groupée : + + +
+ {% csrf_token %} +
+

{{ grouped_order }}

+ {% if error_message %}

{{ error_message }}

{% endif %} + {% for item in grouped_order.item_set.all %} + +
+ {% endfor %} +
+ +
+ + diff --git a/la_chariotte/order/templates/order/grouped_order_orga.html b/la_chariotte/order/templates/order/grouped_order_orga.html new file mode 100644 index 0000000..636891a --- /dev/null +++ b/la_chariotte/order/templates/order/grouped_order_orga.html @@ -0,0 +1,34 @@ + + + + + Commandes groupées {{ grouped_order.id }} + + +

Vue d'organisation de la commande groupée {{ grouped_order.id }}, avec les produits commandés et les commandes individuelles

+
+

{{ grouped_order }} +

Organisateur·ice : {{ grouped_order.orga }}

+

Date de livraison : {{ grouped_order.date }}

+ + les produits commandés pour cette commande groupée : + + + les commandes passées pour cette commande groupée : + + + Retour à la page de commande + + diff --git a/la_chariotte/order/templates/order/index.html b/la_chariotte/order/templates/order/index.html new file mode 100644 index 0000000..433e46a --- /dev/null +++ b/la_chariotte/order/templates/order/index.html @@ -0,0 +1,25 @@ + + + + + Commandes groupées - index + + +

Index des commandes que l'utilisateur·ice connecté·e organise

+

Pour l'instant, index de toutes les commandes groupées qui existent

+ + {% if grouped_order_list %} + + {% else %} +

Pas de commande groupée pour l'instant

+ {% endif %} + + diff --git a/la_chariotte/order/tests.py b/la_chariotte/order/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/la_chariotte/order/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/la_chariotte/order/urls.py b/la_chariotte/order/urls.py new file mode 100644 index 0000000..74c6383 --- /dev/null +++ b/la_chariotte/order/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + +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 diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py new file mode 100644 index 0000000..9c90b4b --- /dev/null +++ b/la_chariotte/order/views.py @@ -0,0 +1,56 @@ +from django.shortcuts import render, get_object_or_404, get_list_or_404 +from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.urls import reverse +from django.views import generic + +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' + + def get_queryset(self): + """Return some 10 grouped orders.""" # changer envoyer les plus proches d'aujourd'hui dans le futur + return Grouped_order.objects.order_by('-date')[:10] + + +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' + + +class GroupedOrderOrgaView(generic.DetailView): + """Vue de supervision d'une commande groupée""" + model = Grouped_order + 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 + grouped_order = get_object_or_404(Grouped_order, pk=grouped_order_id) + try: + 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.", + }) + else: + 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,))) + +def compute_ordered_nb(item): + """Calcule le nombre de produits de ce produit commandés (pour cette commande groupée)""" + ordered_nb = 0 + for order in item.orders.all(): + ordered_nb += order.nb + item.ordered_nb = ordered_nb + item.save() diff --git a/la_chariotte/readMe.md b/la_chariotte/readMe.md new file mode 100644 index 0000000..6e6799f --- /dev/null +++ b/la_chariotte/readMe.md @@ -0,0 +1,166 @@ +Exemple de MCD en markdown avec mermaid : + + +```mermaid +erDiagram +CourseSubmission{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField title +DateField date +FSMField state +} +CourseSubmissionProduct{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +PositiveSmallIntegerField quantity +} +Pricing{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField name +} +Product{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField name +CharField code +BooleanField submission_enabled +} +ProductPrice{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +PositiveSmallIntegerField year +CharField price_type +DecimalField price_flat +PositiveSmallIntegerField price_percent +DecimalField price_percent_minimum +CharField unit +} +ProductPriceRange{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +PositiveSmallIntegerField range_start +PositiveSmallIntegerField range_end +DecimalField unit_price +DecimalField minimum +} +ProductPricePackLine_included_products{ +AutoField id +} +ProductPricePackLine{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField quantity_type +PositiveSmallIntegerField quantity +} +Contract{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +DateField start +DateField end +FileField file +} +Transaction_products{ +AutoField id +} +Transaction{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +PositiveIntegerField debit +PositiveIntegerField credit +BooleanField unlimited_credit +} +Quote{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField external_ref +FSMField state +} +QuoteLine{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +TextField label +DecimalField unit_price +DecimalField quantity +} +Invoice{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField external_ref +FSMField state +} +InvoiceLine{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +TextField label +DecimalField unit_price +DecimalField quantity +} +User{ +CharField password +DateTimeField last_login +BooleanField is_superuser +CharField username +CharField first_name +CharField last_name +EmailField email +BooleanField is_staff +BooleanField is_active +DateTimeField date_joined +UUIDField id +DateTimeField created_on +DateTimeField updated_on +ManyToManyField groups +ManyToManyField user_permissions +} +Organization{ +UUIDField id +DateTimeField created_on +DateTimeField updated_on +CharField code +CharField representative +FileField signature +FileField logo +} +CourseSubmission||--|{User : user +CourseSubmission||--|{Organization : organization +CourseSubmissionProduct||--|{CourseSubmission : course_submission +CourseSubmissionProduct||--|{Product : product +ProductPrice||--|{Pricing : pricing +ProductPrice||--|{Product : product +ProductPriceRange||--|{ProductPrice : product_price +ProductPricePackLine_included_products||--|{ProductPricePackLine : productpricepackline +ProductPricePackLine_included_products||--|{Product : product +ProductPricePackLine||--|{ProductPrice : product_price +ProductPricePackLine}|--|{Product : included_products +Contract||--|{Organization : organization +Contract||--|{Pricing : pricing +Transaction_products||--|{Transaction : transaction +Transaction_products||--|{Product : product +Transaction||--|{Invoice : invoice +Transaction||--|{CourseSubmission : course_submission +Transaction}|--|{Product : products +Quote||--|{Organization : organization +QuoteLine||--|{Quote : quote +QuoteLine||--|{Product : product +Invoice||--|{Organization : organization +InvoiceLine||--|{Invoice : invoice +InvoiceLine||--|{Product : product +Organization||--|{User : representative_user +Organization||--|{Organization : parent + +``` diff --git a/la_chariotte/settings.py b/la_chariotte/settings.py index 68ea23d..fe657cb 100644 --- a/la_chariotte/settings.py +++ b/la_chariotte/settings.py @@ -31,6 +31,7 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + 'la_chariotte.order', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -75,8 +76,12 @@ WSGI_APPLICATION = 'la_chariotte.wsgi.application' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'chariotte-db', + 'USER': 'laetitia', + 'PASSWORD': 'toto', + 'HOST': '127.0.0.1', + 'PORT': '5432', } } @@ -105,7 +110,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Europe/Paris' USE_I18N = True diff --git a/la_chariotte/urls.py b/la_chariotte/urls.py index 1da3016..ebc0a7d 100644 --- a/la_chariotte/urls.py +++ b/la_chariotte/urls.py @@ -14,8 +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 +from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), + path('commande/', include('la_chariotte.order.urls')), ] From e979a0e22739a1a3604997b6c5b6ef64e97c4d29 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Fri, 17 Mar 2023 16:21:42 +0100 Subject: [PATCH 02/18] ajout de ordered_item a l'admin --- la_chariotte/order/admin.py | 3 ++- ...rdereditem_item_alter_ordereditem_order.py | 24 +++++++++++++++++++ la_chariotte/order/models.py | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 la_chariotte/order/migrations/0007_alter_ordereditem_item_alter_ordereditem_order.py diff --git a/la_chariotte/order/admin.py b/la_chariotte/order/admin.py index 817ee91..0d4d29e 100644 --- a/la_chariotte/order/admin.py +++ b/la_chariotte/order/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin -from .models import Grouped_order, Order, Item +from .models import Grouped_order, Order, Item, OrderedItem admin.site.register(Grouped_order) admin.site.register(Order) admin.site.register(Item) +admin.site.register(OrderedItem) diff --git a/la_chariotte/order/migrations/0007_alter_ordereditem_item_alter_ordereditem_order.py b/la_chariotte/order/migrations/0007_alter_ordereditem_item_alter_ordereditem_order.py new file mode 100644 index 0000000..4af4531 --- /dev/null +++ b/la_chariotte/order/migrations/0007_alter_ordereditem_item_alter_ordereditem_order.py @@ -0,0 +1,24 @@ +# Generated by Django 4.1.7 on 2023-03-17 15:09 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0006_item_ordered_nb'), + ] + + operations = [ + migrations.AlterField( + model_name='ordereditem', + name='item', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='order.item'), + ), + migrations.AlterField( + model_name='ordereditem', + name='order', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ordered_items', to='order.order'), + ), + ] diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 7714ad6..44a2619 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -26,3 +26,5 @@ class OrderedItem(models.Model): 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}" From c5d9b45f431acc632525d35aaf873fa7e703e1d8 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Fri, 17 Mar 2023 17:39:25 +0100 Subject: [PATCH 03/18] fonction is_ongoing et tests --- la_chariotte/order/models.py | 10 ++++++++++ la_chariotte/order/tests.py | 26 ++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 44a2619..5bec1f1 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -1,9 +1,19 @@ +import datetime + 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 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}" diff --git a/la_chariotte/order/tests.py b/la_chariotte/order/tests.py index 7ce503c..37453f1 100644 --- a/la_chariotte/order/tests.py +++ b/la_chariotte/order/tests.py @@ -1,3 +1,25 @@ -from django.test import TestCase +import datetime + +from django.test import TestCase +from django.utils import timezone +from .models import Grouped_order + + +class GroupedOrderModelTests(TestCase): + + def test_is_ongoing_with_ongoing_grouped_order(self): + """ + is_ongoing() returns True if the deadline is not crossed + """ + deadline = timezone.now() + datetime.timedelta(days=10) + ongoing_gr_order = Grouped_order(deadline=deadline) + self.assertIs(ongoing_gr_order.is_ongoing(), True) + + def test_is_ongoing_with_old_grouped_order(self): + """ + is_ongoing() returns False if the deadline is crossed + """ + deadline = timezone.now() - datetime.timedelta(hours=1) + ongoing_gr_order = Grouped_order(deadline=deadline) + self.assertIs(ongoing_gr_order.is_ongoing(), False) -# Create your tests here. From 95a0e3fcddfe5c9f87909f9d787fafa1f4055fde Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 23 Mar 2023 15:28:02 +0100 Subject: [PATCH 04/18] gitignore with pycache files --- .gitignore | 3 +-- la_chariotte/__pycache__/__init__.cpython-310.pyc | Bin 176 -> 0 bytes 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 la_chariotte/__pycache__/__init__.cpython-310.pyc diff --git a/.gitignore b/.gitignore index 5e25d49..5c78ec5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ - __pycache__/ coverage.xml .coverage -la_chariotte.egg-info/ \ No newline at end of file +la_chariotte.egg-info/ diff --git a/la_chariotte/__pycache__/__init__.cpython-310.pyc b/la_chariotte/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index ece3371812d1f4c0f755bcb9a06544db66d52a9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmd1j<>g`kf_>*0Q$X}%5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_;enx(7s(wyl zYDs2EW}?1JesXDUYF Date: Thu, 23 Mar 2023 18:00:00 +0100 Subject: [PATCH 05/18] =?UTF-8?q?les=20commandes=20group=C3=A9es=20sont=20?= =?UTF-8?q?list=C3=A9es=20selon=20leurs=20dates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0008_grouped_order_deadline.py | 20 ++++++ ...rename_date_grouped_order_delivery_date.py | 18 +++++ la_chariotte/order/models.py | 2 +- la_chariotte/order/templates/order/index.html | 32 +++++++-- la_chariotte/order/tests.py | 67 +++++++++++++++++++ la_chariotte/order/views.py | 10 ++- 6 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 la_chariotte/order/migrations/0008_grouped_order_deadline.py create mode 100644 la_chariotte/order/migrations/0009_rename_date_grouped_order_delivery_date.py diff --git a/la_chariotte/order/migrations/0008_grouped_order_deadline.py b/la_chariotte/order/migrations/0008_grouped_order_deadline.py new file mode 100644 index 0000000..ae453d0 --- /dev/null +++ b/la_chariotte/order/migrations/0008_grouped_order_deadline.py @@ -0,0 +1,20 @@ +# Generated by Django 4.1.7 on 2023-03-23 14:38 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0007_alter_ordereditem_item_alter_ordereditem_order'), + ] + + operations = [ + migrations.AddField( + model_name='grouped_order', + name='deadline', + field=models.DateTimeField(default=datetime.datetime(2023, 3, 23, 14, 38, 17, 365192, tzinfo=datetime.timezone.utc), verbose_name='Date limite de commande'), + preserve_default=False, + ), + ] diff --git a/la_chariotte/order/migrations/0009_rename_date_grouped_order_delivery_date.py b/la_chariotte/order/migrations/0009_rename_date_grouped_order_delivery_date.py new file mode 100644 index 0000000..1998119 --- /dev/null +++ b/la_chariotte/order/migrations/0009_rename_date_grouped_order_delivery_date.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-23 17:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0008_grouped_order_deadline'), + ] + + operations = [ + migrations.RenameField( + model_name='grouped_order', + old_name='date', + new_name='delivery_date', + ), + ] diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 5bec1f1..ef55d2e 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -7,7 +7,7 @@ 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 - date = models.DateField('Date de livraison') + delivery_date = models.DateField('Date de livraison') deadline = models.DateTimeField('Date limite de commande') def is_ongoing(self): diff --git a/la_chariotte/order/templates/order/index.html b/la_chariotte/order/templates/order/index.html index 433e46a..0961efc 100644 --- a/la_chariotte/order/templates/order/index.html +++ b/la_chariotte/order/templates/order/index.html @@ -8,16 +8,40 @@

Index des commandes que l'utilisateur·ice connecté·e organise

Pour l'instant, index de toutes les commandes groupées qui existent

- {% if grouped_order_list %} + {% if grouped_order_list.incoming_grouped_orders or grouped_order_list.orders_over_grouped_orders or grouped_order_list.old_grouped_orders %} + {% if grouped_order_list.incoming_grouped_orders %} +

Commandes groupées à venir :

+ {% endif %} + {% if grouped_order_list.orders_over_grouped_orders %} +

Livraison à venir, date limite de commande dépassée :

+
    + {% for gr_order in grouped_order_list.orders_over_grouped_orders %} +
  • + {{gr_order.name}} +
  • + {% endfor %} +
+ {% endif %} + {% if grouped_order_list.old_grouped_orders %} +

Livraison passée :

+
    + {% for gr_order in grouped_order_list.old_grouped_orders %} +
  • + {{gr_order.name}} +
  • + {% endfor %} +
+ {% endif %} {% else %}

Pas de commande groupée pour l'instant

{% endif %} diff --git a/la_chariotte/order/tests.py b/la_chariotte/order/tests.py index 37453f1..06df934 100644 --- a/la_chariotte/order/tests.py +++ b/la_chariotte/order/tests.py @@ -1,10 +1,22 @@ import datetime from django.test import TestCase +from django.urls import reverse from django.utils import timezone from .models import Grouped_order +def create_grouped_order(days_before_delivery_date,days_before_deadline,name): + """ + Create a question with the given `question_text` and published the + given number of `days` offset to now (negative for questions published + in the past, positive for questions that have yet to be published). + """ + 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) + + class GroupedOrderModelTests(TestCase): def test_is_ongoing_with_ongoing_grouped_order(self): @@ -23,3 +35,58 @@ class GroupedOrderModelTests(TestCase): ongoing_gr_order = Grouped_order(deadline=deadline) self.assertIs(ongoing_gr_order.is_ongoing(), False) +class GroupedOrderIndexViewTests(TestCase): + + def test_no_grouped_orders(self): + """ + If no grouped order exist, an appropriate message is displayed + """ + response = self.client.get(reverse('order:index')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Pas de commande groupée pour l'instant") + self.assertQuerysetEqual(response.context['grouped_order_list']['old_grouped_orders'], []) + self.assertQuerysetEqual(response.context['grouped_order_list']['orders_over_grouped_orders'], []) + self.assertQuerysetEqual(response.context['grouped_order_list']['incoming_grouped_orders'], []) + + def test_grouped_orders_in_right_section(self): + """ + According to their delivery date and deadline, grouped orders are placed in correct section + """ + 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") + passed_deadline_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="passée") + response = self.client.get(reverse('order:index')) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "Pas de commande groupée pour l'instant") + self.assertContains(response, "Commandes groupées à venir") + self.assertContains(response, "Livraison à venir") + self.assertContains(response, "Livraison passée") + self.assertIs(len(response.context['grouped_order_list']['old_grouped_orders']), 1) + self.assertIs(len(response.context['grouped_order_list']['orders_over_grouped_orders']), 1) + self.assertIs(len(response.context['grouped_order_list']['incoming_grouped_orders']), 1) + + def test_grouped_orders_in_right_section__with_only_passed(self): + """ + According to their delivery date and deadline, grouped orders are placed in correct section + """ + passed_deadline_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="passée") + response = self.client.get(reverse('order:index')) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "Pas de commande groupée pour l'instant") + self.assertNotContains(response, "Commandes groupées à venir") + self.assertNotContains(response, "Livraison à venir") + self.assertContains(response, "Livraison passée") + self.assertIs(len(response.context['grouped_order_list']['old_grouped_orders']), 1) + + def test_grouped_orders_in_right_section__with_only_future(self): + """ + According to their delivery date and deadline, grouped orders are placed in correct section + """ + future_grouped_order = create_grouped_order(days_before_delivery_date=5,days_before_deadline=2,name="future") + response = self.client.get(reverse('order:index')) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, "Pas de commande groupée pour l'instant") + self.assertNotContains(response, "Commandes groupées à venir") + self.assertContains(response, "Livraison à venir") + self.assertNotContains(response, "Livraison passée") + self.assertIs(len(response.context['grouped_order_list']['incoming_grouped_orders']), 1) \ No newline at end of file diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 9c90b4b..72ce0ec 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -1,6 +1,7 @@ from django.shortcuts import render, get_object_or_404, get_list_or_404 from django.http import HttpResponse, Http404, HttpResponseRedirect from django.urls import reverse +from django.utils import timezone from django.views import generic from .models import Grouped_order, Item, Order, OrderedItem @@ -12,8 +13,13 @@ class IndexView(generic.ListView): context_object_name = 'grouped_order_list' def get_queryset(self): - """Return some 10 grouped orders.""" # changer envoyer les plus proches d'aujourd'hui dans le futur - return Grouped_order.objects.order_by('-date')[:10] + """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) + """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) + """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} class GroupedOrderDetailView(generic.DetailView): From 2c5eda993ad8e2f31ecd822396c0329619fc4ad6 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 14:14:32 +0200 Subject: [PATCH 06/18] install pytest --- la_chariotte/order/tests.py | 92 ------------------------- la_chariotte/order/tests/test_models.py | 24 +++++++ la_chariotte/order/tests/test_views.py | 81 ++++++++++++++++++++++ setup.cfg | 2 + 4 files changed, 107 insertions(+), 92 deletions(-) delete mode 100644 la_chariotte/order/tests.py create mode 100644 la_chariotte/order/tests/test_models.py create mode 100644 la_chariotte/order/tests/test_views.py create mode 100644 setup.cfg diff --git a/la_chariotte/order/tests.py b/la_chariotte/order/tests.py deleted file mode 100644 index 06df934..0000000 --- a/la_chariotte/order/tests.py +++ /dev/null @@ -1,92 +0,0 @@ -import datetime - -from django.test import TestCase -from django.urls import reverse -from django.utils import timezone -from .models import Grouped_order - - -def create_grouped_order(days_before_delivery_date,days_before_deadline,name): - """ - Create a question with the given `question_text` and published the - given number of `days` offset to now (negative for questions published - in the past, positive for questions that have yet to be published). - """ - 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) - - -class GroupedOrderModelTests(TestCase): - - def test_is_ongoing_with_ongoing_grouped_order(self): - """ - is_ongoing() returns True if the deadline is not crossed - """ - deadline = timezone.now() + datetime.timedelta(days=10) - ongoing_gr_order = Grouped_order(deadline=deadline) - self.assertIs(ongoing_gr_order.is_ongoing(), True) - - def test_is_ongoing_with_old_grouped_order(self): - """ - is_ongoing() returns False if the deadline is crossed - """ - deadline = timezone.now() - datetime.timedelta(hours=1) - ongoing_gr_order = Grouped_order(deadline=deadline) - self.assertIs(ongoing_gr_order.is_ongoing(), False) - -class GroupedOrderIndexViewTests(TestCase): - - def test_no_grouped_orders(self): - """ - If no grouped order exist, an appropriate message is displayed - """ - response = self.client.get(reverse('order:index')) - self.assertEqual(response.status_code, 200) - self.assertContains(response, "Pas de commande groupée pour l'instant") - self.assertQuerysetEqual(response.context['grouped_order_list']['old_grouped_orders'], []) - self.assertQuerysetEqual(response.context['grouped_order_list']['orders_over_grouped_orders'], []) - self.assertQuerysetEqual(response.context['grouped_order_list']['incoming_grouped_orders'], []) - - def test_grouped_orders_in_right_section(self): - """ - According to their delivery date and deadline, grouped orders are placed in correct section - """ - 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") - passed_deadline_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="passée") - response = self.client.get(reverse('order:index')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "Pas de commande groupée pour l'instant") - self.assertContains(response, "Commandes groupées à venir") - self.assertContains(response, "Livraison à venir") - self.assertContains(response, "Livraison passée") - self.assertIs(len(response.context['grouped_order_list']['old_grouped_orders']), 1) - self.assertIs(len(response.context['grouped_order_list']['orders_over_grouped_orders']), 1) - self.assertIs(len(response.context['grouped_order_list']['incoming_grouped_orders']), 1) - - def test_grouped_orders_in_right_section__with_only_passed(self): - """ - According to their delivery date and deadline, grouped orders are placed in correct section - """ - passed_deadline_gr_order = create_grouped_order(days_before_delivery_date=-1, days_before_deadline=-3,name="passée") - response = self.client.get(reverse('order:index')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "Pas de commande groupée pour l'instant") - self.assertNotContains(response, "Commandes groupées à venir") - self.assertNotContains(response, "Livraison à venir") - self.assertContains(response, "Livraison passée") - self.assertIs(len(response.context['grouped_order_list']['old_grouped_orders']), 1) - - def test_grouped_orders_in_right_section__with_only_future(self): - """ - According to their delivery date and deadline, grouped orders are placed in correct section - """ - future_grouped_order = create_grouped_order(days_before_delivery_date=5,days_before_deadline=2,name="future") - response = self.client.get(reverse('order:index')) - self.assertEqual(response.status_code, 200) - self.assertNotContains(response, "Pas de commande groupée pour l'instant") - self.assertNotContains(response, "Commandes groupées à venir") - self.assertContains(response, "Livraison à venir") - self.assertNotContains(response, "Livraison passée") - self.assertIs(len(response.context['grouped_order_list']['incoming_grouped_orders']), 1) \ No newline at end of file diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py new file mode 100644 index 0000000..2977c49 --- /dev/null +++ b/la_chariotte/order/tests/test_models.py @@ -0,0 +1,24 @@ +import datetime + +from django.utils import timezone +from la_chariotte.order.models import Grouped_order + + + +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 + """ + deadline = timezone.now() + datetime.timedelta(days=10) + ongoing_gr_order = Grouped_order(deadline=deadline) + assert ongoing_gr_order.is_ongoing() == True + + def test_is_ongoing_with_old_grouped_order(self): + """ + is_ongoing() returns False if the deadline is crossed + """ + 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 diff --git a/la_chariotte/order/tests/test_views.py b/la_chariotte/order/tests/test_views.py new file mode 100644 index 0000000..1074ae3 --- /dev/null +++ b/la_chariotte/order/tests/test_views.py @@ -0,0 +1,81 @@ +import datetime + +import pytest + +from django.urls import reverse +from django.utils import timezone +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): + """ + 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) + +class TestGroupedOrderIndexView: + def test_no_grouped_orders(self,client): + """ + If no grouped order exist, an appropriate message is displayed + """ + 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 + + 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')) + 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 + + 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')) + 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 + + 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')) + 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 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..fdfd88b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[tool:pytest] +DJANGO_SETTINGS_MODULE = la_chariotte.settings \ No newline at end of file From c06e48c7fda159f43e21c0eda33d3908b3a71c54 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 14:15:11 +0200 Subject: [PATCH 07/18] maj readMe --- la_chariotte/readMe.md | 166 ----------------------------------------- readMe.md | 10 ++- 2 files changed, 9 insertions(+), 167 deletions(-) delete mode 100644 la_chariotte/readMe.md diff --git a/la_chariotte/readMe.md b/la_chariotte/readMe.md deleted file mode 100644 index 6e6799f..0000000 --- a/la_chariotte/readMe.md +++ /dev/null @@ -1,166 +0,0 @@ -Exemple de MCD en markdown avec mermaid : - - -```mermaid -erDiagram -CourseSubmission{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField title -DateField date -FSMField state -} -CourseSubmissionProduct{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -PositiveSmallIntegerField quantity -} -Pricing{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField name -} -Product{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField name -CharField code -BooleanField submission_enabled -} -ProductPrice{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -PositiveSmallIntegerField year -CharField price_type -DecimalField price_flat -PositiveSmallIntegerField price_percent -DecimalField price_percent_minimum -CharField unit -} -ProductPriceRange{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -PositiveSmallIntegerField range_start -PositiveSmallIntegerField range_end -DecimalField unit_price -DecimalField minimum -} -ProductPricePackLine_included_products{ -AutoField id -} -ProductPricePackLine{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField quantity_type -PositiveSmallIntegerField quantity -} -Contract{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -DateField start -DateField end -FileField file -} -Transaction_products{ -AutoField id -} -Transaction{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -PositiveIntegerField debit -PositiveIntegerField credit -BooleanField unlimited_credit -} -Quote{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField external_ref -FSMField state -} -QuoteLine{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -TextField label -DecimalField unit_price -DecimalField quantity -} -Invoice{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField external_ref -FSMField state -} -InvoiceLine{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -TextField label -DecimalField unit_price -DecimalField quantity -} -User{ -CharField password -DateTimeField last_login -BooleanField is_superuser -CharField username -CharField first_name -CharField last_name -EmailField email -BooleanField is_staff -BooleanField is_active -DateTimeField date_joined -UUIDField id -DateTimeField created_on -DateTimeField updated_on -ManyToManyField groups -ManyToManyField user_permissions -} -Organization{ -UUIDField id -DateTimeField created_on -DateTimeField updated_on -CharField code -CharField representative -FileField signature -FileField logo -} -CourseSubmission||--|{User : user -CourseSubmission||--|{Organization : organization -CourseSubmissionProduct||--|{CourseSubmission : course_submission -CourseSubmissionProduct||--|{Product : product -ProductPrice||--|{Pricing : pricing -ProductPrice||--|{Product : product -ProductPriceRange||--|{ProductPrice : product_price -ProductPricePackLine_included_products||--|{ProductPricePackLine : productpricepackline -ProductPricePackLine_included_products||--|{Product : product -ProductPricePackLine||--|{ProductPrice : product_price -ProductPricePackLine}|--|{Product : included_products -Contract||--|{Organization : organization -Contract||--|{Pricing : pricing -Transaction_products||--|{Transaction : transaction -Transaction_products||--|{Product : product -Transaction||--|{Invoice : invoice -Transaction||--|{CourseSubmission : course_submission -Transaction}|--|{Product : products -Quote||--|{Organization : organization -QuoteLine||--|{Quote : quote -QuoteLine||--|{Product : product -Invoice||--|{Organization : organization -InvoiceLine||--|{Invoice : invoice -InvoiceLine||--|{Product : product -Organization||--|{User : representative_user -Organization||--|{Organization : parent - -``` diff --git a/readMe.md b/readMe.md index 0575610..910b1a4 100644 --- a/readMe.md +++ b/readMe.md @@ -1,3 +1,11 @@ # La Chariotte -La Chariotte est une application web sous licence libre Affera GPL, développée et maintenue par [Hashbang](https://hashbang.fr/). \ No newline at end of file +La Chariotte est une application web sous licence libre Affera GPL, développée et maintenue par [Hashbang](https://hashbang.fr/). + +## Lancer les tests + +Avec pytest, + +```bash +pytest +``` From e522b59ff3797068260b0a399baff86e1bf461a6 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 14:44:14 +0200 Subject: [PATCH 08/18] install black and format files --- la_chariotte/asgi.py | 2 +- la_chariotte/order/apps.py | 4 +- la_chariotte/order/models.py | 37 ++++++-- la_chariotte/order/tests/test_models.py | 6 +- la_chariotte/order/tests/test_views.py | 115 +++++++++++++++++------- la_chariotte/order/urls.py | 16 ++-- la_chariotte/order/views.py | 62 +++++++++---- la_chariotte/settings.py | 82 ++++++++--------- la_chariotte/urls.py | 6 +- la_chariotte/wsgi.py | 2 +- manage.py | 4 +- pyproject.toml | 14 ++- readMe.md | 6 ++ 13 files changed, 238 insertions(+), 118 deletions(-) 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 . +``` From 5a5b4ccb25b9b910e1198d6fdcbaa1faa484d712 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 14:51:09 +0200 Subject: [PATCH 09/18] install flake8 and check files --- la_chariotte/order/models.py | 2 -- la_chariotte/order/tests/test_models.py | 2 +- la_chariotte/order/views.py | 4 ++-- readMe.md | 4 ++-- setup.cfg | 6 +++++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 4088d2f..07051cb 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -1,5 +1,3 @@ -import datetime - from django.db import models from django.utils import timezone diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py index b36d2e3..2f6d89e 100644 --- a/la_chariotte/order/tests/test_models.py +++ b/la_chariotte/order/tests/test_models.py @@ -13,7 +13,7 @@ class TestGroupedOrdersModel: """ deadline = timezone.now() + datetime.timedelta(days=10) ongoing_gr_order = Grouped_order(deadline=deadline) - assert ongoing_gr_order.is_ongoing() == True + assert ongoing_gr_order.is_ongoing() def test_is_ongoing_with_old_grouped_order(self): """ diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 8a969c0..8ed7154 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -1,5 +1,5 @@ -from django.shortcuts import render, get_object_or_404, get_list_or_404 -from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.shortcuts import render, get_object_or_404 +from django.http import HttpResponseRedirect from django.urls import reverse from django.utils import timezone from django.views import generic diff --git a/readMe.md b/readMe.md index 574c3ec..0b08ea9 100644 --- a/readMe.md +++ b/readMe.md @@ -2,9 +2,9 @@ La Chariotte est une application web sous licence libre Affera GPL, développée et maintenue par [Hashbang](https://hashbang.fr/). -## Lancer les tests +## Lancer les tests et linter -Avec pytest, +Lancer les tests avec pytest : ```bash pytest diff --git a/setup.cfg b/setup.cfg index fdfd88b..1183a37 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,6 @@ [tool:pytest] -DJANGO_SETTINGS_MODULE = la_chariotte.settings \ No newline at end of file +DJANGO_SETTINGS_MODULE = la_chariotte.settings + +[flake8] +exclude = migrations +extend-ignore = E501 \ No newline at end of file From 8275915a55cd64ad41cbbfc94fe1a41e496f50ea Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 16:08:54 +0200 Subject: [PATCH 10/18] install flake8-black and move pytest config in pyproject.toml --- pyproject.toml | 3 +++ readMe.md | 9 ++++++++- setup.cfg | 3 --- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b165014..a8c7d06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,9 @@ description = "Web application for organising grouped orders" readme = "readMe.md" license = {file = "LICENSE"} +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "la_chariotte.settings" + [tool.black] line-length = 88 exclude = ''' diff --git a/readMe.md b/readMe.md index 0b08ea9..b41147b 100644 --- a/readMe.md +++ b/readMe.md @@ -10,8 +10,15 @@ Lancer les tests avec pytest : pytest ``` -Formater les fichiers avec black : +Vérifier le formatage des fichers avec flake8 : + +```bash +flake8 +``` + +Si l'erreur `BLK100 Blakck would make changes.` apparait, c'est utile de lancer black pour formater les fichiers : ```bash black . ``` + diff --git a/setup.cfg b/setup.cfg index 1183a37..f0a1c3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[tool:pytest] -DJANGO_SETTINGS_MODULE = la_chariotte.settings - [flake8] exclude = migrations extend-ignore = E501 \ No newline at end of file From 50e72f7235287640453adea9da7ac076027833a1 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Thu, 30 Mar 2023 16:35:52 +0200 Subject: [PATCH 11/18] install pytest-isort and isort and sort files --- la_chariotte/order/admin.py | 2 +- la_chariotte/order/models.py | 1 - la_chariotte/order/tests/test_models.py | 1 + la_chariotte/order/tests/test_views.py | 2 +- la_chariotte/order/views.py | 2 +- la_chariotte/urls.py | 2 +- pyproject.toml | 5 +++++ readMe.md | 7 ++++++- 8 files changed, 16 insertions(+), 6 deletions(-) diff --git a/la_chariotte/order/admin.py b/la_chariotte/order/admin.py index 0d4d29e..09e2a24 100644 --- a/la_chariotte/order/admin.py +++ b/la_chariotte/order/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Grouped_order, Order, Item, OrderedItem +from .models import Grouped_order, Item, Order, OrderedItem admin.site.register(Grouped_order) admin.site.register(Order) diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 07051cb..c936bf6 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -1,5 +1,4 @@ from django.db import models - from django.utils import timezone diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py index 2f6d89e..374d5d2 100644 --- a/la_chariotte/order/tests/test_models.py +++ b/la_chariotte/order/tests/test_models.py @@ -1,6 +1,7 @@ import datetime from django.utils import timezone + from la_chariotte.order.models import Grouped_order diff --git a/la_chariotte/order/tests/test_views.py b/la_chariotte/order/tests/test_views.py index b3d9364..a1073ea 100644 --- a/la_chariotte/order/tests/test_views.py +++ b/la_chariotte/order/tests/test_views.py @@ -1,9 +1,9 @@ import datetime import pytest - from django.urls import reverse from django.utils import timezone + from la_chariotte.order.models import Grouped_order pytestmark = pytest.mark.django_db diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 8ed7154..7872019 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -1,5 +1,5 @@ -from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone from django.views import generic diff --git a/la_chariotte/urls.py b/la_chariotte/urls.py index 6bea7b4..cdef8bf 100644 --- a/la_chariotte/urls.py +++ b/la_chariotte/urls.py @@ -14,7 +14,7 @@ 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 include, path urlpatterns = [ path("admin/", admin.site.urls), diff --git a/pyproject.toml b/pyproject.toml index a8c7d06..34ac446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,11 @@ license = {file = "LICENSE"} [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "la_chariotte.settings" +addopts = "--isort" +isort_ignore = ["*migrations/*.py"] + +[tool.isort] +skip_glob = ["*migrations/*.py"] [tool.black] line-length = 88 diff --git a/readMe.md b/readMe.md index b41147b..7d49e43 100644 --- a/readMe.md +++ b/readMe.md @@ -10,13 +10,18 @@ Lancer les tests avec pytest : pytest ``` +Si il y a des erreurs ISORT, on peut lancer isort pour trier les fichiers : +```bash +isort . +``` + Vérifier le formatage des fichers avec flake8 : ```bash flake8 ``` -Si l'erreur `BLK100 Blakck would make changes.` apparait, c'est utile de lancer black pour formater les fichiers : +Si l'erreur `BLK100 Blakck would make changes.` apparait, on peut lancer black pour formater les fichiers : ```bash black . From 1835640799042aeb213f9a7c4335c71a82a8e82c Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Wed, 5 Apr 2023 10:27:46 +0200 Subject: [PATCH 12/18] use pip-compile to generate requirements.txt --- pyproject.toml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 34ac446..51ef109 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,25 @@ version = "0.0.1" description = "Web application for organising grouped orders" readme = "readMe.md" license = {file = "LICENSE"} +dependencies = [ + "django>=4,<5", + "psycopg2>=2,<3", +] + +[build-system] +requires = [ + "setuptools","wheel" +] +build-backend = "setuptools.build_meta" + +[project.optional-dependencies] +dev = [ + "pytest>=7,<8", + "flake8-black<1", + "pip-tools>=6,<7", + "pytest-isort>=3,<4", + "pytest-django>=4,<5", +] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "la_chariotte.settings" From 026c978be0fcd0c457e58c1f75d11b66ca317477 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Wed, 5 Apr 2023 11:12:58 +0200 Subject: [PATCH 13/18] create gitlab-ci.yml --- .coveragerc | 10 ++++++++++ la_chariotte/settings.py | 16 ++++++++++------ pyproject.toml | 6 +++++- readMe.md | 8 ++++++-- 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..73d8e64 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +omit = + */migrations/* + */admin.py + */tests/* + */urls.py + */apps.py + la_chariotte/settings.py + la_chariotte/asgi.py + la_chariotte/wsgi.py diff --git a/la_chariotte/settings.py b/la_chariotte/settings.py index 9de6adb..159b46b 100644 --- a/la_chariotte/settings.py +++ b/la_chariotte/settings.py @@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/4.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ - +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -77,14 +77,18 @@ WSGI_APPLICATION = "la_chariotte.wsgi.application" DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - "NAME": "chariotte-db", - "USER": "laetitia", - "PASSWORD": "toto", - "HOST": "127.0.0.1", - "PORT": "5432", + "NAME": os.getenv("DB_NAME", "chariotte-db"), } } +if os.getenv("DB_USER"): + DATABASES["default"]["USER"] = os.getenv("DB_USER") +if os.getenv("DB_PASSWORD"): + DATABASES["default"]["PASSWORD"] = os.getenv("DB_PASSWORD") +if os.getenv("DB_HOST"): + DATABASES["default"]["HOST"] = os.getenv("DB_HOST") +if os.getenv("DB_PORT"): + DATABASES["default"]["PORT"] = os.getenv("DB_PORT") # Password validation # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators diff --git a/pyproject.toml b/pyproject.toml index 51ef109..37f8bb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ version = "0.0.1" description = "Web application for organising grouped orders" readme = "readMe.md" license = {file = "LICENSE"} +authors = [{name = "Hashbang", email = "support@hashbang.fr"}] dependencies = [ "django>=4,<5", "psycopg2>=2,<3", @@ -22,11 +23,14 @@ dev = [ "pip-tools>=6,<7", "pytest-isort>=3,<4", "pytest-django>=4,<5", + "pytest-cov>=4,<5", + "diff-cover>=4,<5", + "pytest-black<1", ] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "la_chariotte.settings" -addopts = "--isort" +addopts = "--isort --black --reuse-db --cov-report xml --cov-report term-missing --cov=la_chariotte -p no:warnings" isort_ignore = ["*migrations/*.py"] [tool.isort] diff --git a/readMe.md b/readMe.md index 7d49e43..b0e366f 100644 --- a/readMe.md +++ b/readMe.md @@ -15,13 +15,17 @@ Si il y a des erreurs ISORT, on peut lancer isort pour trier les fichiers : isort . ``` -Vérifier le formatage des fichers avec flake8 : +Si il y a des erreurs BLACK, on peut lancer black pour linter le code : +```bash +black . +``` +Vérifier le formatage des fichers avec flake8 : ```bash flake8 ``` -Si l'erreur `BLK100 Blakck would make changes.` apparait, on peut lancer black pour formater les fichiers : +Si l'erreur `BLK100 Black would make changes.` apparait, on peut lancer black pour formater les fichiers : ```bash black . From 1b10530c5ca22bcc5c1c373d7ce2e487de032e65 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Wed, 5 Apr 2023 19:12:33 +0200 Subject: [PATCH 14/18] delete flake8 --- pyproject.toml | 1 - readMe.md | 12 ------------ setup.cfg | 3 --- 3 files changed, 16 deletions(-) delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml index 37f8bb5..68763da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ build-backend = "setuptools.build_meta" [project.optional-dependencies] dev = [ "pytest>=7,<8", - "flake8-black<1", "pip-tools>=6,<7", "pytest-isort>=3,<4", "pytest-django>=4,<5", diff --git a/readMe.md b/readMe.md index b0e366f..50f999a 100644 --- a/readMe.md +++ b/readMe.md @@ -19,15 +19,3 @@ Si il y a des erreurs BLACK, on peut lancer black pour linter le code : ```bash black . ``` - -Vérifier le formatage des fichers avec flake8 : -```bash -flake8 -``` - -Si l'erreur `BLK100 Black would make changes.` apparait, on peut lancer black pour formater les fichiers : - -```bash -black . -``` - diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f0a1c3b..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -exclude = migrations -extend-ignore = E501 \ No newline at end of file From 8648fee7866d90ab573bee474bdcfc85ff9fdddb Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Fri, 7 Apr 2023 15:54:07 +0200 Subject: [PATCH 15/18] tests on GroupedOrderDetailView --- la_chariotte/order/models.py | 8 ++-- .../templates/order/grouped_order_detail.html | 4 +- .../templates/order/grouped_order_orga.html | 2 +- la_chariotte/order/tests/test_views.py | 47 ++++++++++++++++++- la_chariotte/order/views.py | 3 +- 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index c936bf6..da7fd1d 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -12,7 +12,7 @@ class Grouped_order(models.Model): """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): + def __str__(self): # pragma: no cover return ( self.name if self.name @@ -28,7 +28,7 @@ class Order(models.Model): max_length=100, verbose_name="Personne qui passe la commande" ) # a changer, utiliser ForeignKey de user - def __str__(self): + def __str__(self): # pragma: no cover return f"Commande de {self.author} pour la commande groupée {self.grouped_order.pk}" @@ -39,7 +39,7 @@ class Item(models.Model): ) # à transformer en manytomany quand il y aura un catalogue ordered_nb = models.IntegerField(default=0) - def __str__(self): + def __str__(self): # pragma: no cover return f"{self.name} dans la commande groupée {self.grouped_order.pk}" @@ -52,5 +52,5 @@ class OrderedItem(models.Model): ) item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="orders") - def __str__(self): + def __str__(self): # pragma: no cover return f"{self.nb} {self.item}, dans la commande {self.order.pk}" diff --git a/la_chariotte/order/templates/order/grouped_order_detail.html b/la_chariotte/order/templates/order/grouped_order_detail.html index db14d26..d6b1dfd 100644 --- a/la_chariotte/order/templates/order/grouped_order_detail.html +++ b/la_chariotte/order/templates/order/grouped_order_detail.html @@ -9,7 +9,7 @@

{{ grouped_order }}

Organisateur·ice : {{ grouped_order.orga }}

-

Date de livraison : {{ grouped_order.date }}

+

Date de livraison : {{ grouped_order.delivery_date }}

les produits disponibles pour cette commande groupée : @@ -30,7 +30,7 @@
{% endfor %} - + diff --git a/la_chariotte/order/templates/order/grouped_order_orga.html b/la_chariotte/order/templates/order/grouped_order_orga.html index 636891a..0cee30b 100644 --- a/la_chariotte/order/templates/order/grouped_order_orga.html +++ b/la_chariotte/order/templates/order/grouped_order_orga.html @@ -9,7 +9,7 @@

{{ grouped_order }}

Organisateur·ice : {{ grouped_order.orga }}

-

Date de livraison : {{ grouped_order.date }}

+

Date de livraison : {{ grouped_order.delivery_date }}

les produits commandés pour cette commande groupée :
    diff --git a/la_chariotte/order/tests/test_views.py b/la_chariotte/order/tests/test_views.py index a1073ea..9f5fa4f 100644 --- a/la_chariotte/order/tests/test_views.py +++ b/la_chariotte/order/tests/test_views.py @@ -4,7 +4,7 @@ import pytest from django.urls import reverse from django.utils import timezone -from la_chariotte.order.models import Grouped_order +from la_chariotte.order.models import Grouped_order, Item, Order pytestmark = pytest.mark.django_db @@ -130,3 +130,48 @@ class TestGroupedOrderIndexView: response.context["grouped_order_list"]["incoming_grouped_orders"][0] == future_grouped_order ) + + +class TestGroupedOrderDetailView: + def test_order_item(self, client): + """ + From the OrderDetailView, we order an item using the order form, and it creates an Order woth and Ordered_item inside + """ + grouped_order = create_grouped_order( + days_before_delivery_date=5, days_before_deadline=2, name="gr order test" + ) + item = Item.objects.create(name="test item", grouped_order=grouped_order) + detail_url = reverse( + "order:grouped_order_detail", + kwargs={ + "pk": grouped_order.pk, + }, + ) + response = client.get(detail_url) + assert response.status_code == 200 + assert "test item" in response.content.decode() + assert "gr order test" in response.content.decode() + assert item.ordered_nb == 0 + order_url = reverse( + "order:order", + kwargs={ + "grouped_order_id": grouped_order.pk, + }, + ) + response = client.post( + order_url, + { + "item": item.pk, + }, + ) + assert response.status_code == 302 + assert response.url == reverse( + "order:grouped_order_orga", + kwargs={ + "pk": grouped_order.pk, + }, + ) + item.refresh_from_db() + assert item.ordered_nb == 1 + order = Order.objects.first() + assert order.ordered_items.count() == 1 diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 7872019..4af00e7 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -73,6 +73,7 @@ def order( 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) + selected_item.save() # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. @@ -82,7 +83,7 @@ def order( def compute_ordered_nb(item): - """Calcule le nombre de produits de ce produit commandés (pour cette commande groupée)""" + """Calcule le nombre de produits de ce type commandés (pour cette commande groupée)""" ordered_nb = 0 for order in item.orders.all(): ordered_nb += order.nb From 743940df3b18b0cd275e14f8adaeb048fb437f9e Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Tue, 11 Apr 2023 16:15:35 +0200 Subject: [PATCH 16/18] update readme --- readMe.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/readMe.md b/readMe.md index 50f999a..b50408a 100644 --- a/readMe.md +++ b/readMe.md @@ -1,11 +1,25 @@ # La Chariotte +## Présentation + La Chariotte est une application web sous licence libre Affera GPL, développée et maintenue par [Hashbang](https://hashbang.fr/). -## Lancer les tests et linter +## Développement + +Cloner le projet : +```bash +git clone https://gitlab.com/hashbangfr/la_chariotte.git +``` + +Installer les dépendances : +```bash +pip install -r requirements.txt +pip install -r dev-requirements.txt +``` + +## Lancer les tests Lancer les tests avec pytest : - ```bash pytest ``` From 6c3ed24b747003d7823c4371f1095074c9c1da63 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Tue, 11 Apr 2023 16:46:34 +0200 Subject: [PATCH 17/18] camelcase for GroupedOrder --- la_chariotte/order/admin.py | 4 ++-- .../0010_rename_grouped_order_groupedorder.py | 16 ++++++++++++++ la_chariotte/order/models.py | 6 ++--- .../templates/order/grouped_order_detail.html | 22 +++++++++---------- la_chariotte/order/tests/test_models.py | 6 ++--- la_chariotte/order/tests/test_views.py | 4 ++-- la_chariotte/order/views.py | 15 +++++++------ 7 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 la_chariotte/order/migrations/0010_rename_grouped_order_groupedorder.py diff --git a/la_chariotte/order/admin.py b/la_chariotte/order/admin.py index 09e2a24..5aab8b9 100644 --- a/la_chariotte/order/admin.py +++ b/la_chariotte/order/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin -from .models import Grouped_order, Item, Order, OrderedItem +from .models import GroupedOrder, Item, Order, OrderedItem -admin.site.register(Grouped_order) +admin.site.register(GroupedOrder) admin.site.register(Order) admin.site.register(Item) admin.site.register(OrderedItem) diff --git a/la_chariotte/order/migrations/0010_rename_grouped_order_groupedorder.py b/la_chariotte/order/migrations/0010_rename_grouped_order_groupedorder.py new file mode 100644 index 0000000..e068ba2 --- /dev/null +++ b/la_chariotte/order/migrations/0010_rename_grouped_order_groupedorder.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2 on 2023-04-11 14:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("order", "0009_rename_date_grouped_order_delivery_date"), + ] + + operations = [ + migrations.RenameModel( + old_name="Grouped_order", + new_name="GroupedOrder", + ), + ] diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index da7fd1d..9bf026d 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -2,7 +2,7 @@ from django.db import models from django.utils import timezone -class Grouped_order(models.Model): +class GroupedOrder(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") @@ -22,7 +22,7 @@ class Grouped_order(models.Model): class Order(models.Model): grouped_order = models.ForeignKey( - Grouped_order, on_delete=models.CASCADE, related_name="order_set" + GroupedOrder, on_delete=models.CASCADE, related_name="order_set" ) author = models.CharField( max_length=100, verbose_name="Personne qui passe la commande" @@ -35,7 +35,7 @@ class Order(models.Model): class Item(models.Model): name = models.CharField(max_length=100) grouped_order = models.ForeignKey( - Grouped_order, on_delete=models.CASCADE + GroupedOrder, on_delete=models.CASCADE ) # à transformer en manytomany quand il y aura un catalogue ordered_nb = models.IntegerField(default=0) diff --git a/la_chariotte/order/templates/order/grouped_order_detail.html b/la_chariotte/order/templates/order/grouped_order_detail.html index d6b1dfd..09613de 100644 --- a/la_chariotte/order/templates/order/grouped_order_detail.html +++ b/la_chariotte/order/templates/order/grouped_order_detail.html @@ -21,16 +21,16 @@ {% endfor %}
- {% csrf_token %} -
-

{{ grouped_order }}

- {% if error_message %}

{{ error_message }}

{% endif %} - {% for item in grouped_order.item_set.all %} - -
- {% endfor %} -
- -
+ {% csrf_token %} +
+

{{ grouped_order }}

+ {% if error_message %}

{{ error_message }}

{% endif %} + {% for item in grouped_order.item_set.all %} + +
+ {% endfor %} +
+ + diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py index 374d5d2..11a08b0 100644 --- a/la_chariotte/order/tests/test_models.py +++ b/la_chariotte/order/tests/test_models.py @@ -2,7 +2,7 @@ import datetime from django.utils import timezone -from la_chariotte.order.models import Grouped_order +from la_chariotte.order.models import GroupedOrder class TestGroupedOrdersModel: @@ -13,7 +13,7 @@ class TestGroupedOrdersModel: is_ongoing() returns True if the deadline is not crossed """ deadline = timezone.now() + datetime.timedelta(days=10) - ongoing_gr_order = Grouped_order(deadline=deadline) + ongoing_gr_order = GroupedOrder(deadline=deadline) assert ongoing_gr_order.is_ongoing() def test_is_ongoing_with_old_grouped_order(self): @@ -21,5 +21,5 @@ class TestGroupedOrdersModel: is_ongoing() returns False if the deadline is crossed """ deadline = timezone.now() - datetime.timedelta(hours=1) - ongoing_gr_order = Grouped_order(deadline=deadline) + ongoing_gr_order = GroupedOrder(deadline=deadline) 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 9f5fa4f..a4339ea 100644 --- a/la_chariotte/order/tests/test_views.py +++ b/la_chariotte/order/tests/test_views.py @@ -4,7 +4,7 @@ import pytest from django.urls import reverse from django.utils import timezone -from la_chariotte.order.models import Grouped_order, Item, Order +from la_chariotte.order.models import GroupedOrder, Item, Order pytestmark = pytest.mark.django_db @@ -15,7 +15,7 @@ def create_grouped_order(days_before_delivery_date, days_before_deadline, name): """ 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( + return GroupedOrder.objects.create( name=name, orga="test orga", delivery_date=date, deadline=deadline ) diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 4af00e7..86b57c6 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -4,7 +4,7 @@ from django.urls import reverse from django.utils import timezone from django.views import generic -from .models import Grouped_order, Item, Order, OrderedItem +from .models import GroupedOrder, Item, Order, OrderedItem class IndexView(generic.ListView): @@ -15,19 +15,19 @@ class IndexView(generic.ListView): def get_queryset(self): """Return the 5 most recent old grouped orders""" - old_grouped_orders = Grouped_order.objects.filter( + old_grouped_orders = GroupedOrder.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()) + GroupedOrder.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( + incoming_grouped_orders = GroupedOrder.objects.filter( deadline__gte=timezone.now() ).order_by( "deadline" @@ -42,21 +42,22 @@ class IndexView(generic.ListView): class GroupedOrderDetailView(generic.DetailView): """Vue de détail d'une commande groupée - possibilité de commander si elle est en cours""" - model = Grouped_order + model = GroupedOrder template_name = "order/grouped_order_detail.html" + context_object_name = "grouped_order" class GroupedOrderOrgaView(generic.DetailView): """Vue de supervision d'une commande groupée""" - model = Grouped_order + model = GroupedOrder 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 - grouped_order = get_object_or_404(Grouped_order, pk=grouped_order_id) + grouped_order = get_object_or_404(GroupedOrder, pk=grouped_order_id) try: selected_item = grouped_order.item_set.get(pk=request.POST["item"]) except (KeyError, Item.DoesNotExist): From 0e3f0a3e661fa78435b7d9c2ec876c299970c253 Mon Sep 17 00:00:00 2001 From: Laetitia Getti Date: Wed, 12 Apr 2023 11:49:51 +0200 Subject: [PATCH 18/18] is_to_be_delivered method and refactoring order views --- .gitlab-ci.yml | 8 ++--- la_chariotte/order/models.py | 4 +++ la_chariotte/order/templates/order/index.html | 6 ++-- la_chariotte/order/tests/test_models.py | 29 +++++++++++++++++-- la_chariotte/order/tests/test_views.py | 26 +++++++++++++---- la_chariotte/order/views.py | 21 +++++++++----- 6 files changed, 73 insertions(+), 21 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 425e5c1..c4732e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,8 +36,8 @@ tests: - source $CACHE_PATH/venv/bin/activate - pip install -U -r requirements.txt - pip install -U -r dev-requirements.txt - # - pytest --create-db --cov --cov-report=xml - # - if [ "$CI_COMMIT_REF_NAME" = 'main' ] ; then exit 0 ; fi - # - if [ "$CI_COMMIT_REF_NAME" = 'develop' ] ; git fetch origin main ; then diff-cover coverage.xml --fail-under=90 && exit 0 ; fi - # - git fetch origin develop ; diff-cover coverage.xml --fail-under=90 --compare-branch origin/develop + - pytest --create-db --cov --cov-report=xml + - if [ "$CI_COMMIT_REF_NAME" = 'main' ] ; then exit 0 ; fi + - if [ "$CI_COMMIT_REF_NAME" = 'develop' ] ; git fetch origin main ; then diff-cover coverage.xml --fail-under=90 && exit 0 ; fi + - git fetch origin develop ; diff-cover coverage.xml --fail-under=90 --compare-branch origin/develop - echo "Tests done." diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py index 9bf026d..4c4881f 100644 --- a/la_chariotte/order/models.py +++ b/la_chariotte/order/models.py @@ -12,6 +12,10 @@ class GroupedOrder(models.Model): """Returns True if the grouped order is open for new Orders - False if it's too late""" return self.deadline >= timezone.now() + def is_to_be_delivered(self): + """Returns True if the grouped order has not been delivered yet - False if it's old""" + return self.delivery_date >= timezone.now().date() + def __str__(self): # pragma: no cover return ( self.name diff --git a/la_chariotte/order/templates/order/index.html b/la_chariotte/order/templates/order/index.html index 0961efc..26060cd 100644 --- a/la_chariotte/order/templates/order/index.html +++ b/la_chariotte/order/templates/order/index.html @@ -8,7 +8,7 @@

Index des commandes que l'utilisateur·ice connecté·e organise

Pour l'instant, index de toutes les commandes groupées qui existent

- {% if grouped_order_list.incoming_grouped_orders or grouped_order_list.orders_over_grouped_orders or grouped_order_list.old_grouped_orders %} + {% if grouped_order_list.incoming_grouped_orders or grouped_order_list.crossed_deadline_grouped_orders or grouped_order_list.old_grouped_orders %} {% if grouped_order_list.incoming_grouped_orders %}

Commandes groupées à venir :

    @@ -20,10 +20,10 @@ {% endfor %}
{% endif %} - {% if grouped_order_list.orders_over_grouped_orders %} + {% if grouped_order_list.crossed_deadline_grouped_orders %}

Livraison à venir, date limite de commande dépassée :

    - {% for gr_order in grouped_order_list.orders_over_grouped_orders %} + {% for gr_order in grouped_order_list.crossed_deadline_grouped_orders %}
  • {{gr_order.name}} diff --git a/la_chariotte/order/tests/test_models.py b/la_chariotte/order/tests/test_models.py index 11a08b0..d5fff17 100644 --- a/la_chariotte/order/tests/test_models.py +++ b/la_chariotte/order/tests/test_models.py @@ -21,5 +21,30 @@ class TestGroupedOrdersModel: is_ongoing() returns False if the deadline is crossed """ deadline = timezone.now() - datetime.timedelta(hours=1) - ongoing_gr_order = GroupedOrder(deadline=deadline) - assert not ongoing_gr_order.is_ongoing() + old_gr_order = GroupedOrder(deadline=deadline) + assert not old_gr_order.is_ongoing() + + def test_is_to_be_delivered_with_today_delivery(self): + """ + is_to_be_delivered() returns True if the delivery date is today + """ + del_date = timezone.now().date() + gr_order = GroupedOrder(delivery_date=del_date) + assert gr_order.is_to_be_delivered() + + def test_is_to_be_delivered_with_old_grouped_order(self): + """ + is_to_be_delivered() returns False if the delivery date is passed + """ + del_date = timezone.now().date() - datetime.timedelta(days=1) + old_gr_order = GroupedOrder(delivery_date=del_date) + assert not old_gr_order.is_to_be_delivered() + + def test_is_to_be_delivered_with_crossed_deadline_grouped_order(self): + """ + is_to_be_delivered() returns True even if the deadline is crossed + """ + deadline = timezone.now() - datetime.timedelta(hours=1) + del_date = timezone.now().date() + datetime.timedelta(days=1) + old_gr_order = GroupedOrder(deadline=deadline, delivery_date=del_date) + assert old_gr_order.is_to_be_delivered() diff --git a/la_chariotte/order/tests/test_views.py b/la_chariotte/order/tests/test_views.py index a4339ea..9e241bc 100644 --- a/la_chariotte/order/tests/test_views.py +++ b/la_chariotte/order/tests/test_views.py @@ -30,7 +30,11 @@ class TestGroupedOrderIndexView: 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"]) + len( + response.context["grouped_order_list"][ + "crossed_deadline_grouped_orders" + ] + ) == 0 ) assert ( @@ -60,7 +64,11 @@ class TestGroupedOrderIndexView: 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"]) + len( + response.context["grouped_order_list"][ + "crossed_deadline_grouped_orders" + ] + ) == 1 ) assert ( @@ -71,7 +79,7 @@ class TestGroupedOrderIndexView: == old_gr_order ) assert ( - response.context["grouped_order_list"]["orders_over_grouped_orders"][0] + response.context["grouped_order_list"]["crossed_deadline_grouped_orders"][0] == crossed_deadline_gr_order ) assert ( @@ -94,7 +102,11 @@ class TestGroupedOrderIndexView: 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"]) + len( + response.context["grouped_order_list"][ + "crossed_deadline_grouped_orders" + ] + ) == 0 ) assert ( @@ -120,7 +132,11 @@ class TestGroupedOrderIndexView: 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"]) + len( + response.context["grouped_order_list"][ + "crossed_deadline_grouped_orders" + ] + ) == 0 ) assert ( diff --git a/la_chariotte/order/views.py b/la_chariotte/order/views.py index 86b57c6..8c0e6e1 100644 --- a/la_chariotte/order/views.py +++ b/la_chariotte/order/views.py @@ -14,27 +14,34 @@ class IndexView(generic.ListView): context_object_name = "grouped_order_list" def get_queryset(self): + """3 grouped_order status : incoming, crossed_deadline, and old""" + now = timezone.now() + today = now.date() + """Return the 5 most recent old grouped orders""" old_grouped_orders = GroupedOrder.objects.filter( - delivery_date__lt=timezone.now().date() + # is_to_be_delivered=False + delivery_date__lt=today ).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 = ( - GroupedOrder.objects.filter(delivery_date__gte=timezone.now().date()) - .filter(deadline__lt=timezone.now()) + + """Return all grouped orders, that have crossed their ordering deadline but the delivery date is still to come""" + crossed_dealine_grouped_orders = ( + GroupedOrder.objects.filter(delivery_date__gte=today) + .filter(deadline__lt=now) .order_by("-delivery_date") ) # delivery date >= today (not delivered) and deadline < today (we cannot order) + """Return all incoming grouped orders""" incoming_grouped_orders = GroupedOrder.objects.filter( - deadline__gte=timezone.now() + deadline__gte=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, + "crossed_deadline_grouped_orders": crossed_dealine_grouped_orders, "incoming_grouped_orders": incoming_grouped_orders, }