forked from DGNum/gestiojeux
Add loan tables
Add two views for loans : - inventory:ongoing_loans - inventory:all_loans (permission inventory.can_see_loan_details required)
This commit is contained in:
parent
d8551052fd
commit
00148cd5ca
15 changed files with 156 additions and 6 deletions
|
@ -26,6 +26,7 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django_cas_ng",
|
"django_cas_ng",
|
||||||
|
"django_tables2",
|
||||||
"markdownx",
|
"markdownx",
|
||||||
"haystack",
|
"haystack",
|
||||||
"website",
|
"website",
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-05-06 14:23
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0003_gameloan'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='category',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'étagère'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='gameloan',
|
||||||
|
options={'ordering': ['borrow_date'], 'permissions': [('can_see_loan_details', 'Can see loan details')], 'verbose_name': 'emprunt', 'verbose_name_plural': 'emprunts'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gameloan',
|
||||||
|
name='borrow_date',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='Date d’emprunt'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gameloan',
|
||||||
|
name='return_date',
|
||||||
|
field=models.DateTimeField(null=True, verbose_name='Date de retour'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -162,3 +162,5 @@ class GameLoan(AbstractLoan):
|
||||||
|
|
||||||
class Meta(AbstractLoan.Meta):
|
class Meta(AbstractLoan.Meta):
|
||||||
abstract = False
|
abstract = False
|
||||||
|
permissions = [("can_see_loan_details", "Can see loan details")]
|
||||||
|
|
||||||
|
|
33
inventory/tables.py
Normal file
33
inventory/tables.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.urls import reverse
|
||||||
|
from .models import GameLoan
|
||||||
|
|
||||||
|
class LoanTable(tables.Table):
|
||||||
|
next_pattern = "inventory:all_loans"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = GameLoan
|
||||||
|
sequence = ("lent_object", "borrow_date", "return_date", "mail")
|
||||||
|
exclude = ("id",)
|
||||||
|
slug = tables.Column(verbose_name="Actions", orderable=False)
|
||||||
|
|
||||||
|
def render_lent_object(self, value, record):
|
||||||
|
return format_html("<a href='{}'>{}</a>",
|
||||||
|
reverse("inventory:game", args=[record.lent_object.slug]), value)
|
||||||
|
|
||||||
|
def render_slug(self, value, record):
|
||||||
|
res = ""
|
||||||
|
if record.return_date == None:
|
||||||
|
res = format_html("<a class='button' href='{}?next={}'>Rendre le jeu</a>",
|
||||||
|
reverse("inventory:return_game", args=[value]),
|
||||||
|
reverse(self.next_pattern))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class OngoingLoansTable(LoanTable):
|
||||||
|
next_pattern = "inventory:ongoing_loans"
|
||||||
|
|
||||||
|
class Meta(LoanTable.Meta):
|
||||||
|
exclude = ("return_date", "mail", "id")
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
<a class="button" href="{% url "inventory:ongoing_loans" %}">
|
||||||
|
Emprunts en cours
|
||||||
|
</a>
|
||||||
<a class="button" href="{% url "inventory:category_list" %}">
|
<a class="button" href="{% url "inventory:category_list" %}">
|
||||||
Liste des étagères
|
Liste des étagères
|
||||||
<p class="helptext">
|
<p class="helptext">
|
||||||
|
|
9
inventory/templates/inventory/loans/loans_table.html
Normal file
9
inventory/templates/inventory/loans/loans_table.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block "content" %}
|
||||||
|
<h1>Liste des emprunts</h1>
|
||||||
|
{% render_table table %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block "main_attributes" %}class="wide-main"{% endblock %}
|
7
inventory/templates/inventory/loans/ongoing.html
Normal file
7
inventory/templates/inventory/loans/ongoing.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block "content" %}
|
||||||
|
<h1>Emprunts en cours</h1>
|
||||||
|
{% render_table table %}
|
||||||
|
{% endblock %}
|
|
@ -13,6 +13,8 @@ from .views import (
|
||||||
GameLoanView,
|
GameLoanView,
|
||||||
BorrowGameView,
|
BorrowGameView,
|
||||||
ReturnGameView,
|
ReturnGameView,
|
||||||
|
OngoingLoansView,
|
||||||
|
DetailLoanView,
|
||||||
)
|
)
|
||||||
|
|
||||||
app_name = "inventory"
|
app_name = "inventory"
|
||||||
|
@ -34,7 +36,9 @@ urlpatterns = [
|
||||||
name="modify_game_comment",
|
name="modify_game_comment",
|
||||||
),
|
),
|
||||||
path("search/", InventorySearchView.as_view(), name="search"),
|
path("search/", InventorySearchView.as_view(), name="search"),
|
||||||
path("loans/<slug>/", GameLoanView.as_view(), name="game_loan"),
|
path("loans/game/<slug>/", GameLoanView.as_view(), name="game_loan"),
|
||||||
path("loans/return/<slug>/", ReturnGameView.as_view(), name="return_game"),
|
path("loans/return/<slug>/", ReturnGameView.as_view(), name="return_game"),
|
||||||
path("loans/borrow/<slug>/", BorrowGameView.as_view(), name="borrow_game"),
|
path("loans/borrow/<slug>/", BorrowGameView.as_view(), name="borrow_game"),
|
||||||
|
path("loans/ongoing/", OngoingLoansView.as_view(), name="ongoing_loans"),
|
||||||
|
path("loans/all/", DetailLoanView.as_view(), name="all_loans"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
from django.views.generic import TemplateView, ListView, DetailView
|
from django.views.generic import TemplateView, ListView, DetailView
|
||||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from haystack.generic_views import SearchView
|
from haystack.generic_views import SearchView
|
||||||
from haystack.forms import SearchForm
|
from haystack.forms import SearchForm
|
||||||
from haystack.query import SearchQuerySet
|
from haystack.query import SearchQuerySet
|
||||||
|
from django_tables2.views import SingleTableView
|
||||||
from comments.views import AddCommentView, ModifyCommentView
|
from comments.views import AddCommentView, ModifyCommentView
|
||||||
from loans.views import BorrowView, ReturnView, DetailLoanView
|
from loans.views import BorrowView, ReturnView, DetailLoanView
|
||||||
from .models import Category, Tag, Game, GameComment, GameLoan
|
from .models import Category, Tag, Game, GameComment, GameLoan
|
||||||
from .forms import BorrowGameForm
|
from .forms import BorrowGameForm
|
||||||
|
from .tables import LoanTable, OngoingLoansTable
|
||||||
|
|
||||||
|
|
||||||
class InventoryView(TemplateView):
|
class InventoryView(TemplateView):
|
||||||
|
@ -67,6 +70,7 @@ class ModifyGameCommentView(ModifyCommentView):
|
||||||
template_name = "inventory/game.html"
|
template_name = "inventory/game.html"
|
||||||
success_pattern_name = "inventory:game"
|
success_pattern_name = "inventory:game"
|
||||||
|
|
||||||
|
|
||||||
class BorrowGameView(BorrowView):
|
class BorrowGameView(BorrowView):
|
||||||
model = Game
|
model = Game
|
||||||
loan_model = GameLoan
|
loan_model = GameLoan
|
||||||
|
@ -74,12 +78,27 @@ class BorrowGameView(BorrowView):
|
||||||
form_class = BorrowGameForm
|
form_class = BorrowGameForm
|
||||||
success_pattern_name = "inventory:game_loan"
|
success_pattern_name = "inventory:game_loan"
|
||||||
|
|
||||||
|
|
||||||
class ReturnGameView(ReturnView):
|
class ReturnGameView(ReturnView):
|
||||||
model = GameLoan
|
model = GameLoan
|
||||||
pattern_name = "inventory:game_loan"
|
pattern_name = "inventory:game_loan"
|
||||||
|
|
||||||
|
|
||||||
class GameLoanView(DetailLoanView):
|
class GameLoanView(DetailLoanView):
|
||||||
model = Game
|
model = Game
|
||||||
loan_model = GameLoan
|
loan_model = GameLoan
|
||||||
template_name = "inventory/loans/game_loan.html"
|
template_name = "inventory/loans/game_loan.html"
|
||||||
|
|
||||||
|
|
||||||
|
class OngoingLoansView(SingleTableView):
|
||||||
|
queryset = GameLoan.ongoing_loans()
|
||||||
|
table_class = OngoingLoansTable
|
||||||
|
template_name = "inventory/loans/ongoing.html"
|
||||||
|
|
||||||
|
|
||||||
|
class DetailLoanView(PermissionRequiredMixin, SingleTableView):
|
||||||
|
permission_required = "inventory.can_see_loan_details"
|
||||||
|
model = GameLoan
|
||||||
|
table_class = LoanTable
|
||||||
|
template_name = "inventory/loans/loans_table.html"
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ from django.utils.timezone import now
|
||||||
class AbstractLoan(models.Model):
|
class AbstractLoan(models.Model):
|
||||||
lent_object = None # Fill this with a foreign key in subclasses
|
lent_object = None # Fill this with a foreign key in subclasses
|
||||||
slug = AutoSlugField(unique=True, populate_from="lent_object")
|
slug = AutoSlugField(unique=True, populate_from="lent_object")
|
||||||
borrow_date = models.DateTimeField(auto_now_add=True)
|
borrow_date = models.DateTimeField(
|
||||||
return_date = models.DateTimeField(null=True)
|
auto_now_add=True, verbose_name="Date d’emprunt")
|
||||||
|
return_date = models.DateTimeField(null=True, verbose_name="Date de retour")
|
||||||
mail = models.EmailField()
|
mail = models.EmailField()
|
||||||
|
|
||||||
lent_object_slug_field = "slug"
|
lent_object_slug_field = "slug"
|
||||||
|
|
|
@ -21,6 +21,8 @@ class ReturnView(SingleObjectMixin, RedirectView):
|
||||||
kwargs[self.redirect_slug_field] = getattr(loan.lent_object,
|
kwargs[self.redirect_slug_field] = getattr(loan.lent_object,
|
||||||
loan.lent_object_slug_field)
|
loan.lent_object_slug_field)
|
||||||
messages.success(self.request, "Rendu effectué.")
|
messages.success(self.request, "Rendu effectué.")
|
||||||
|
if "next" in self.request.GET:
|
||||||
|
return self.request.GET["next"]
|
||||||
return super().get_redirect_url(*args, **kwargs)
|
return super().get_redirect_url(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,4 @@ Pillow
|
||||||
django-cas-ng
|
django-cas-ng
|
||||||
django-haystack
|
django-haystack
|
||||||
Whoosh
|
Whoosh
|
||||||
|
django-tables2
|
||||||
|
|
|
@ -31,7 +31,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: $page_width;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
margin: 0 auto 50px;
|
margin: 0 auto 50px;
|
||||||
|
@ -48,6 +47,10 @@ main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main:not(.wide-main){
|
||||||
|
max-width: $page_width;
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
background-color: $footer_bg_color;
|
background-color: $footer_bg_color;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
|
@ -153,6 +156,24 @@ table {
|
||||||
th {
|
th {
|
||||||
border-bottom-width: 2px;
|
border-bottom-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
th.asc::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0de'; // Sort up symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
th.desc::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0dd'; // Sort down symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
th.orderable:not(.asc, .desc)::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0dc'; // Sort up/down symbol
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
|
|
|
@ -277,9 +277,7 @@ body {
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
body {
|
body {
|
||||||
font-size: 12pt; } }
|
font-size: 12pt; } }
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: 850px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
margin: 0 auto 50px;
|
margin: 0 auto 50px;
|
||||||
|
@ -291,6 +289,9 @@ main {
|
||||||
main.small_page {
|
main.small_page {
|
||||||
max-width: 550px; }
|
max-width: 550px; }
|
||||||
|
|
||||||
|
main:not(.wide-main) {
|
||||||
|
max-width: 850px; }
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
background-color: #6bb8c4;
|
background-color: #6bb8c4;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
|
@ -403,6 +404,18 @@ table {
|
||||||
text-align: left; }
|
text-align: left; }
|
||||||
table th {
|
table th {
|
||||||
border-bottom-width: 2px; }
|
border-bottom-width: 2px; }
|
||||||
|
table th.asc::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0de'; }
|
||||||
|
table th.desc::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0dd'; }
|
||||||
|
table th.orderable:not(.asc, .desc)::before {
|
||||||
|
display: inline;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
content: '\f0dc'; }
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
<a {% if url_name == "home" %}class="current"{% endif %} href="{% url "website:home" %}"><i class="fa fa-home" aria-hidden="true"></i></a>
|
<a {% if url_name == "home" %}class="current"{% endif %} href="{% url "website:home" %}"><i class="fa fa-home" aria-hidden="true"></i></a>
|
||||||
<a {% if url_name == "inventory" %}class="current"{% endif %} href="{% url "inventory:inventory" %}">Inventaire</a>
|
<a {% if url_name == "inventory" %}class="current"{% endif %} href="{% url "inventory:inventory" %}">Inventaire</a>
|
||||||
<a {% if url_name == "suggestions" %}class="current"{% endif %} href="{% url "suggestions:suggestions" %}">Suggestions</a>
|
<a {% if url_name == "suggestions" %}class="current"{% endif %} href="{% url "suggestions:suggestions" %}">Suggestions</a>
|
||||||
|
{% if perms.inventory.can_see_loan_details %}
|
||||||
|
<a href={% url "inventory:all_loans" %}>Gestion des emprunts</a>
|
||||||
|
{% endif %}
|
||||||
{% if request.user.is_staff %}
|
{% if request.user.is_staff %}
|
||||||
<a href="{% url "admin:index" %}">Admin</a>
|
<a href="{% url "admin:index" %}">Admin</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue