mirror of
https://framagit.org/la-chariotte/la-chariotte.git
synced 2025-05-01 19:32:26 +02:00
Merge branch 'authentication' into 'develop'
Authentification See merge request hashbangfr/la_chariotte!6
This commit is contained in:
commit
0c7f489331
26 changed files with 359 additions and 38 deletions
|
@ -33,3 +33,10 @@ Si il y a des erreurs BLACK, on peut lancer black pour linter le code :
|
|||
```bash
|
||||
black .
|
||||
```
|
||||
|
||||
## Architecture de l'application
|
||||
|
||||
Les différentes applications Django créées sont :
|
||||
|
||||
- Order, pour gérer tout ce qui tourne autour des commandes
|
||||
- Accounts, pour gérer la création de comptes. Pour la connexion, la déconnexion et le changement de mot de passe, on utilise l'application auth intégrée à Django.
|
3
conftest.py
Normal file
3
conftest.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
pytest_plugins = [
|
||||
"la_chariotte.helpers.fixtures",
|
||||
]
|
|
@ -12,6 +12,12 @@ black==23.3.0
|
|||
# via pytest-black
|
||||
build==0.10.0
|
||||
# via pip-tools
|
||||
chardet==4.0.0
|
||||
# via diff-cover
|
||||
# via flake8-black
|
||||
build==0.10.0
|
||||
# via pip-tools
|
||||
# via pytest-black
|
||||
chardet==4.0.0
|
||||
# via diff-cover
|
||||
click==8.1.3
|
||||
|
@ -32,6 +38,16 @@ inflect==3.0.2
|
|||
# via
|
||||
# diff-cover
|
||||
# jinja2-pluralize
|
||||
flake8==6.0.0
|
||||
# via flake8-black
|
||||
flake8-black==0.3.6
|
||||
# via la-chariotte (pyproject.toml)
|
||||
importlib-metadata==6.1.0
|
||||
# via inflect
|
||||
inflect==3.0.2
|
||||
# via
|
||||
# diff-cover
|
||||
# jinja2-pluralize
|
||||
iniconfig==2.0.0
|
||||
# via pytest
|
||||
isort==5.12.0
|
||||
|
@ -44,6 +60,8 @@ jinja2-pluralize==0.3.0
|
|||
# via diff-cover
|
||||
markupsafe==2.1.2
|
||||
# via jinja2
|
||||
mccabe==0.7.0
|
||||
# via flake8
|
||||
mypy-extensions==1.0.0
|
||||
# via black
|
||||
packaging==23.0
|
||||
|
@ -63,6 +81,13 @@ pluggy==1.0.0
|
|||
# pytest
|
||||
psycopg2-binary==2.9.6
|
||||
# via la-chariotte (pyproject.toml)
|
||||
pygments==2.14.0
|
||||
# via diff-cover
|
||||
# via la-chariotte (pyproject.toml)
|
||||
pycodestyle==2.10.0
|
||||
# via flake8
|
||||
pyflakes==3.0.1
|
||||
# via flake8
|
||||
pygments==2.14.0
|
||||
# via diff-cover
|
||||
pyproject-hooks==1.0.0
|
||||
|
@ -95,6 +120,14 @@ tomli==2.0.1
|
|||
# pytest
|
||||
wheel==0.40.0
|
||||
# via pip-tools
|
||||
zipp==3.15.0
|
||||
# via importlib-metadata
|
||||
# coverage
|
||||
# flake8-black
|
||||
# pyproject-hooks
|
||||
# pytest
|
||||
wheel==0.40.0
|
||||
# via pip-tools
|
||||
zipp==3.15.0
|
||||
# via importlib-metadata
|
||||
|
||||
|
|
0
la_chariotte/accounts/__init__.py
Normal file
0
la_chariotte/accounts/__init__.py
Normal file
6
la_chariotte/accounts/apps.py
Normal file
6
la_chariotte/accounts/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "la_chariotte.accounts"
|
0
la_chariotte/accounts/migrations/__init__.py
Normal file
0
la_chariotte/accounts/migrations/__init__.py
Normal file
3
la_chariotte/accounts/models.py
Normal file
3
la_chariotte/accounts/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
14
la_chariotte/accounts/templates/registration/login.html
Normal file
14
la_chariotte/accounts/templates/registration/login.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Connexion{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Connexion</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Se connecter</button>
|
||||
<a href="{% url 'accounts:signup' %}">Créer un compte</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
13
la_chariotte/accounts/templates/registration/signup.html
Normal file
13
la_chariotte/accounts/templates/registration/signup.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Créer un compte{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Créer un compte</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Valider</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
9
la_chariotte/accounts/urls.py
Normal file
9
la_chariotte/accounts/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.urls import include, path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "accounts"
|
||||
urlpatterns = [
|
||||
path("inscription/", views.SignUpView.as_view(), name="signup"),
|
||||
path("", include("django.contrib.auth.urls")),
|
||||
]
|
9
la_chariotte/accounts/views.py
Normal file
9
la_chariotte/accounts/views.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import generic
|
||||
|
||||
|
||||
class SignUpView(generic.CreateView):
|
||||
form_class = UserCreationForm
|
||||
success_url = reverse_lazy("accounts:login")
|
||||
template_name = "registration/signup.html"
|
18
la_chariotte/helpers/fixtures.py
Normal file
18
la_chariotte/helpers/fixtures.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client_log(client, django_user_model):
|
||||
username = "test@user.fr"
|
||||
password = "azertypassword"
|
||||
user = django_user_model.objects.create_user(username=username, password=password)
|
||||
client.login(username=username, password=password)
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_user(django_user_model):
|
||||
username = "other@user.fr"
|
||||
password = "azertypassword"
|
||||
user = django_user_model.objects.create_user(username=username, password=password)
|
||||
return user
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2 on 2023-04-18 13:08
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("order", "0010_rename_grouped_order_groupedorder"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="groupedorder",
|
||||
name="orga",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,10 +1,15 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
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
|
||||
name = models.CharField(
|
||||
max_length=100, null=True, verbose_name="Titre de la commande"
|
||||
) # optionnal
|
||||
orga = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, verbose_name="Organisateur·ice"
|
||||
)
|
||||
delivery_date = models.DateField("Date de livraison")
|
||||
deadline = models.DateTimeField("Date limite de commande")
|
||||
|
||||
|
|
|
@ -11,7 +11,19 @@
|
|||
<p>Organisateur·ice : {{ grouped_order.orga }}</p>
|
||||
<p>Date de livraison : {{ grouped_order.delivery_date }}</p>
|
||||
|
||||
les produits disponibles pour cette commande groupée :
|
||||
{% if not user.is_authenticated %}
|
||||
<p>Vous êtes l'organisateur·ice de cette commande groupée ?
|
||||
<a href="{% url 'order:grouped_order_overview' grouped_order.id %}">
|
||||
Connectez-vous pour accéder à la page de gestion</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if user == grouped_order.orga %}
|
||||
<a href="{% url 'order:grouped_order_overview' grouped_order.id %}">
|
||||
Page de gestion de la commande groupée</a>
|
||||
{% endif %}
|
||||
|
||||
<p>les produits disponibles pour cette commande groupée : </p>
|
||||
|
||||
<ul>
|
||||
{% for item in grouped_order.item_set.all %}
|
||||
|
|
|
@ -29,6 +29,6 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<a href={% url 'order:order' grouped_order.id %}>Retour à la page de commande</a>
|
||||
<a href={% url 'order:grouped_order_detail' grouped_order.id %}>Retour à la page de commande</a>
|
||||
</body>
|
||||
</html>
|
|
@ -6,7 +6,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<p>Index des commandes que l'utilisateur·ice connecté·e organise</p>
|
||||
<p>Pour l'instant, index de toutes les commandes groupées qui existent</p>
|
||||
|
||||
{% 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 %}
|
||||
|
|
|
@ -13,6 +13,7 @@ class TestGroupedOrdersModel:
|
|||
is_ongoing() returns True if the deadline is not crossed
|
||||
"""
|
||||
deadline = timezone.now() + datetime.timedelta(days=10)
|
||||
|
||||
ongoing_gr_order = GroupedOrder(deadline=deadline)
|
||||
assert ongoing_gr_order.is_ongoing()
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -9,23 +11,35 @@ from la_chariotte.order.models import GroupedOrder, Item, 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, orga_user
|
||||
):
|
||||
"""
|
||||
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 GroupedOrder.objects.create(
|
||||
name=name, orga="test orga", delivery_date=date, deadline=deadline
|
||||
name=name, orga=orga_user, delivery_date=date, deadline=deadline
|
||||
)
|
||||
|
||||
|
||||
class TestGroupedOrderIndexView:
|
||||
def test_no_grouped_orders(self, client):
|
||||
def test_anonymous_user_redirection(self, client):
|
||||
"""
|
||||
If the user is anonymous, they are redirected to login view
|
||||
"""
|
||||
assert auth.get_user(client).is_anonymous
|
||||
response = client.get(reverse("order:index"))
|
||||
assert response.status_code == 302
|
||||
assert response.url.startswith(reverse("accounts:login"))
|
||||
assert response.url.endswith(reverse("order:index"))
|
||||
|
||||
def test_no_grouped_orders(self, client_log):
|
||||
"""
|
||||
If no grouped order exist, an appropriate message is displayed
|
||||
"""
|
||||
response = client.get(reverse("order:index"))
|
||||
response = client_log.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
|
||||
|
@ -41,22 +55,29 @@ class TestGroupedOrderIndexView:
|
|||
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_log):
|
||||
"""
|
||||
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"
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="future",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
crossed_deadline_gr_order = create_grouped_order(
|
||||
days_before_delivery_date=2,
|
||||
days_before_deadline=-1,
|
||||
name="crossed deadline",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
old_gr_order = create_grouped_order(
|
||||
days_before_delivery_date=-1, days_before_deadline=-3, name="old"
|
||||
days_before_delivery_date=-1,
|
||||
days_before_deadline=-3,
|
||||
name="old",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
response = client.get(reverse("order:index"))
|
||||
response = client_log.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()
|
||||
|
@ -87,14 +108,17 @@ class TestGroupedOrderIndexView:
|
|||
== 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_log):
|
||||
"""
|
||||
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"
|
||||
days_before_delivery_date=-1,
|
||||
days_before_deadline=-3,
|
||||
name="passée",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
response = client.get(reverse("order:index"))
|
||||
response = client_log.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()
|
||||
|
@ -117,14 +141,17 @@ class TestGroupedOrderIndexView:
|
|||
== 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_log):
|
||||
"""
|
||||
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"
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="future",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
response = client.get(reverse("order:index"))
|
||||
response = client_log.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()
|
||||
|
@ -147,14 +174,45 @@ class TestGroupedOrderIndexView:
|
|||
== future_grouped_order
|
||||
)
|
||||
|
||||
def test_gr_order_not_organized_by_logged_user(self, client_log, other_user):
|
||||
"""
|
||||
If the logged user is not the organizer of a grouped order they don't see it on IndexView
|
||||
"""
|
||||
logged_user = auth.get_user(client_log)
|
||||
future_grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="future",
|
||||
orga_user=other_user,
|
||||
)
|
||||
assert future_grouped_order.orga is not logged_user
|
||||
response = client_log.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"][
|
||||
"crossed_deadline_grouped_orders"
|
||||
]
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert (
|
||||
len(response.context["grouped_order_list"]["incoming_grouped_orders"]) == 0
|
||||
)
|
||||
|
||||
|
||||
class TestGroupedOrderDetailView:
|
||||
def test_order_item(self, client):
|
||||
def test_order_item(self, client, other_user):
|
||||
"""
|
||||
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"
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=other_user,
|
||||
)
|
||||
item = Item.objects.create(name="test item", grouped_order=grouped_order)
|
||||
detail_url = reverse(
|
||||
|
@ -182,7 +240,7 @@ class TestGroupedOrderDetailView:
|
|||
)
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse(
|
||||
"order:grouped_order_orga",
|
||||
"order:grouped_order_overview",
|
||||
kwargs={
|
||||
"pk": grouped_order.pk,
|
||||
},
|
||||
|
@ -191,3 +249,60 @@ class TestGroupedOrderDetailView:
|
|||
assert item.ordered_nb == 1
|
||||
order = Order.objects.first()
|
||||
assert order.ordered_items.count() == 1
|
||||
|
||||
|
||||
class TestGroupedOrderOrgaView:
|
||||
def test_user_not_logged_redirect(self, client, other_user):
|
||||
"""
|
||||
A user that is not logged cannot see the GroupedOrderOrgaView. They get redirected to the login view
|
||||
"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=other_user,
|
||||
)
|
||||
orga_view_url = reverse(
|
||||
"order:grouped_order_overview",
|
||||
kwargs={
|
||||
"pk": grouped_order.pk,
|
||||
},
|
||||
)
|
||||
assert auth.get_user(client).is_anonymous
|
||||
response = client.get(orga_view_url)
|
||||
assert response.status_code == 302
|
||||
assert response.url.startswith(reverse("accounts:login"))
|
||||
assert response.url.endswith(
|
||||
reverse(
|
||||
"order:grouped_order_overview",
|
||||
kwargs={
|
||||
"pk": grouped_order.pk,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def test_user_not_orga_redirect(self, client_log, other_user):
|
||||
"""
|
||||
A user that is not orga cannot see the GroupedOrderOrgaView.
|
||||
They get a 403 forbidden error
|
||||
"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=other_user,
|
||||
)
|
||||
orga_view_url = reverse(
|
||||
"order:grouped_order_overview",
|
||||
kwargs={
|
||||
"pk": grouped_order.pk,
|
||||
},
|
||||
)
|
||||
detail_view_url = reverse(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={
|
||||
"pk": grouped_order.pk,
|
||||
},
|
||||
)
|
||||
response = client_log.get(orga_view_url)
|
||||
assert response.status_code == 403
|
||||
|
|
|
@ -9,7 +9,9 @@ urlpatterns = [
|
|||
"<int:pk>/", views.GroupedOrderDetailView.as_view(), name="grouped_order_detail"
|
||||
),
|
||||
path(
|
||||
"<int:pk>/orga", views.GroupedOrderOrgaView.as_view(), name="grouped_order_orga"
|
||||
"<int:pk>/gerer",
|
||||
views.GroupedOrderOrgaView.as_view(),
|
||||
name="grouped_order_overview",
|
||||
),
|
||||
path("<int:grouped_order_id>/commander/", views.order, name="order"),
|
||||
]
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.urls import reverse
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
|
||||
from .models import GroupedOrder, 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é"""
|
||||
class IndexView(LoginRequiredMixin, generic.ListView):
|
||||
"""View showing all the grouped orders organized by the authenticated user"""
|
||||
|
||||
template_name = "order/index.html"
|
||||
context_object_name = "grouped_order_list"
|
||||
|
||||
def get_queryset(self):
|
||||
"""Only grouped orders organized by logged user"""
|
||||
logged_user_grouped_orders = GroupedOrder.objects.filter(orga=self.request.user)
|
||||
|
||||
"""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(
|
||||
old_grouped_orders = logged_user_grouped_orders.filter(
|
||||
# is_to_be_delivered=False
|
||||
delivery_date__lt=today
|
||||
).order_by("-delivery_date")[
|
||||
|
@ -28,13 +32,13 @@ class IndexView(generic.ListView):
|
|||
|
||||
"""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)
|
||||
logged_user_grouped_orders.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(
|
||||
incoming_grouped_orders = logged_user_grouped_orders.filter(
|
||||
deadline__gte=now
|
||||
).order_by(
|
||||
"deadline"
|
||||
|
@ -47,18 +51,23 @@ 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"""
|
||||
"""Detail view for a grouped order - possibility to order if it is ongoing - No permissions restriction"""
|
||||
|
||||
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"""
|
||||
class GroupedOrderOrgaView(UserPassesTestMixin, generic.DetailView):
|
||||
"""Overview of a grouped order, for the organizer"""
|
||||
|
||||
model = GroupedOrder
|
||||
template_name = "order/grouped_order_orga.html"
|
||||
template_name = "order/grouped_order_overview.html"
|
||||
context_object_name = "grouped_order"
|
||||
|
||||
def test_func(self):
|
||||
"""Accessible only if the requesting user is the grouped order organizer"""
|
||||
return self.get_object().orga == self.request.user
|
||||
|
||||
|
||||
def order(
|
||||
|
@ -86,7 +95,7 @@ def order(
|
|||
# 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,))
|
||||
reverse("order:grouped_order_overview", args=(grouped_order.pk,))
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ ALLOWED_HOSTS = []
|
|||
|
||||
INSTALLED_APPS = [
|
||||
"la_chariotte.order",
|
||||
"la_chariotte.accounts",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
|
@ -52,10 +53,14 @@ MIDDLEWARE = [
|
|||
|
||||
ROOT_URLCONF = "la_chariotte.urls"
|
||||
|
||||
LOGIN_URL = "accounts:login"
|
||||
LOGIN_REDIRECT_URL = "home"
|
||||
LOGOUT_REDIRECT_URL = "home"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"DIRS": [BASE_DIR / "la_chariotte" / "templates"],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
|
|
13
la_chariotte/templates/base.html
Normal file
13
la_chariotte/templates/base.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{% block title %}{% endblock %} - La Chariotte</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
19
la_chariotte/templates/home.html
Normal file
19
la_chariotte/templates/home.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Accueil{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.is_authenticated %}
|
||||
Hi {{ user.username }}!
|
||||
<p>
|
||||
<a href="{% url 'order:index' %}">Mes commandes groupées</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="{% url 'accounts:logout' %}">Se déconnecter</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p>You are not logged in</p>
|
||||
<a href="{% url 'accounts:login' %}">Se connecter</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -15,8 +15,11 @@ Including another URLconf
|
|||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("commande/", include("la_chariotte.order.urls")),
|
||||
path("comptes/", include("la_chariotte.accounts.urls")),
|
||||
path("", TemplateView.as_view(template_name="home.html"), name="home"),
|
||||
]
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
[project]
|
||||
name = "la chariotte"
|
||||
name = "la_chariotte"
|
||||
version = "0.0.1"
|
||||
description = "Web application for organising grouped orders"
|
||||
authors = [{name = "Laetitia Getti", email = "laetitia@chariotte.fr"}]
|
||||
readme = "readMe.md"
|
||||
license = {file = "LICENSE"}
|
||||
authors = [{name = "Hashbang", email = "support@hashbang.fr"}]
|
||||
classifiers = ["License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"]
|
||||
dependencies = [
|
||||
"django>=4,<5",
|
||||
"psycopg2>=2,<3",
|
||||
"psycopg2-binary>=2,<3",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
|
Loading…
Reference in a new issue