Loading coin/settings_base.py +2 −3 Original line number Diff line number Diff line Loading @@ -166,7 +166,6 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: #'django.contrib.admindocs', 'polymorphic', # 'south', 'autocomplete_light', # Automagic autocomplete foreingkey form component 'activelink', # Detect if a link match actual page 'compat', Loading hardware_provisioning/admin.py +182 −70 Original line number Diff line number Diff line Loading @@ -2,10 +2,12 @@ from __future__ import unicode_literals from django.contrib import admin from django.conf.urls import url from django.shortcuts import get_object_or_404 from django.contrib import admin, messages from django.contrib.auth import get_user_model from django.forms import ModelChoiceField from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect import autocomplete_light from .models import ItemType, Item, Loan, Storage Loading @@ -17,8 +19,47 @@ User = get_user_model() admin.site.register(ItemType) def _give_back_success_messages(items, request): for item in items: messages.success(request, "{} a bien été marqué comme rendu.".format(item)) if len(items) > 1: messages.warning( request, "N'oubliez pas de renseigner, pour chaque retour, le nouvel emprunteur " + "ou le nouveau lieu de stockage.", ) else: messages.warning( request, "N'oubliez pas de renseigner le nouvel emprunteur " + "ou le nouveau lieu de stockage.", ) def give_back_loan(request, id): # could be better : could not be a POST, and could not rely on HTTP_REFERER # We could for that rely on django-inline-actions, but the version # we could use with our Django 1.9 is outdated and buggy # We could also offer an intermediate page to specify the storage loan = get_object_or_404(Loan, pk=id) try: redirect_url = request.META['HTTP_REFERER'] except KeyError: # Fallback if no referer header is present redirect_url = reverse( 'admin:hardware_provisioning_item_change', args=[loan.item.id] ) loan.item.give_back() _give_back_success_messages([loan.item], request) return HttpResponseRedirect(redirect_url) class OwnerFilter(admin.SimpleListFilter): title = "Propriétaire" title = "propriétaire" parameter_name = 'owner' def lookups(self, request, model_admin): Loading Loading @@ -56,14 +97,113 @@ class AvailabilityFilter(admin.SimpleListFilter): return queryset class CurrentLoanInline(admin.TabularInline): model = Loan extra = 0 fields = ('user', 'item', 'short_date', 'notes', 'action_buttons') readonly_fields = ('user', 'item', 'short_date', 'notes', 'action_buttons') verbose_name_plural = "Prêt en cours" show_change_link = True def get_queryset(self, request): qs = super(CurrentLoanInline, self).get_queryset(request) return qs.running() def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False def action_buttons(self, obj): if obj.is_running(): return """<a class="button "href="{}">Déclarer rendu</a>""".format( reverse('admin:loan-give_back', args=[obj.pk])) else: return '' action_buttons.short_description = 'Actions' action_buttons.allow_tags = True class LoanHistoryInline(admin.TabularInline): model = Loan extra = 0 fields = ('user', 'item', 'short_date', 'short_date_end', 'notes') readonly_fields = ('user', 'item', 'short_date', 'short_date_end', 'notes') ordering = ['-loan_date_end'] verbose_name_plural = "Historique de prêt de cet objet" show_change_link = True classes = ['collapse'] # Django >= 1.10-ready def get_queryset(self, request): qs = super(LoanHistoryInline, self).get_queryset(request) return qs.finished() def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class AddLoanInline(admin.StackedInline): model = Loan extra = 1 max_num = 1 fields = ('user', 'item', 'loan_date', 'notes') verbose_name_plural = "Déclarer le prêt de cet objet" classes = ['collapse'] # Django >= 1.10-ready form = autocomplete_light.modelform_factory(Loan, fields='__all__') def get_queryset(self, request): qs = super(AddLoanInline, self).get_queryset(request) return qs.none() def has_delete_permission(self, request, obj=None): return False class BorrowerFilter(admin.SimpleListFilter): title = 'détenteur actuel' parameter_name = 'user' def _filter_loans(self, items_queryset, user_pk=None): qs = Loan.objects.running().filter(item__in=items_queryset) if user_pk is not None: qs.filter(user=user_pk) return qs def lookups(self, request, model_admin): # Get relevant (and authorized) users only relevant_items = model_admin.get_queryset(request) users = set() for loan in self._filter_loans(relevant_items): users.add((loan.user.pk, loan.user)) return users def queryset(self, request, queryset): if self.value(): loans_qs = self._filter_loans(queryset).filter( user__pk=self.value(), ) return queryset.filter(loans__in=loans_qs) else: return queryset @admin.register(Item) class ItemAdmin(admin.ModelAdmin): list_display = ( 'designation', 'mac_address', 'serial', 'owner', 'buy_date', 'deployed', 'is_available', 'storage') 'designation', 'current_borrower', 'get_mac_and_serial', 'deployed', 'is_available', 'storage', 'buy_date', 'owner', ) list_filter = ( AvailabilityFilter, 'type', 'storage', 'buy_date', OwnerFilter) 'buy_date', BorrowerFilter, OwnerFilter) search_fields = ( 'designation', 'mac_address', 'serial', 'owner__email', 'owner__nickname', Loading @@ -73,11 +213,25 @@ class ItemAdmin(admin.ModelAdmin): form = autocomplete_light.modelform_factory(Loan, fields='__all__') inlines = [AddLoanInline, CurrentLoanInline, LoanHistoryInline] def give_back(self, request, queryset): for item in queryset.filter(loans__loan_date_end=None): items = queryset.filter(loans__loan_date_end=None) for item in items: item.give_back() _give_back_success_messages(items, request) give_back.short_description = 'Rendre le matériel' def get_urls(self): urls = super(ItemAdmin, self).get_urls() my_urls = [ url( r'^give_back/(?P<id>.+)$', self.admin_site.admin_view(give_back_loan), name='loan-give_back'), ] return my_urls + urls class StatusFilter(admin.SimpleListFilter): title = 'Statut' Loading Loading @@ -110,54 +264,6 @@ class StatusFilter(admin.SimpleListFilter): return queryset class BorrowerFilter(admin.SimpleListFilter): title = 'Adhérent emprunteur' parameter_name = 'user' def lookups(self, request, model_admin): users = set() for loan in model_admin.get_queryset(request): users.add((loan.user.pk, loan.user)) return users def queryset(self, request, queryset): if self.value(): return queryset.filter(user=self.value()) else: return queryset class ItemChoiceField(ModelChoiceField): # On surcharge cette méthode pour afficher mac et n° de série dans le menu # déroulant de sélection d'un objet dans la création d'un prêt. def label_from_instance(self, obj): return obj.designation + ' ' + obj.get_mac_and_serial() @admin.register(Loan) class LoanAdmin(admin.ModelAdmin): list_display = ('item', 'get_mac_and_serial', 'user', 'loan_date', 'loan_date_end') list_filter = (StatusFilter, BorrowerFilter, 'item__designation') search_fields = ( 'item__designation', 'user__nickname', 'user__username', 'user__first_name', 'user__last_name', ) actions = ['end_loan'] def end_loan(self, request, queryset): queryset.filter(loan_date_end=None).update( loan_date_end=datetime.now()) end_loan.short_description = 'Mettre fin au prêt' form = autocomplete_light.modelform_factory(Loan, fields='__all__') def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'item': kwargs['queryset'] = Item.objects.all() return ItemChoiceField(**kwargs) else: return super(LoanAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) @admin.register(Storage) class StorageAdmin(admin.ModelAdmin): list_display = ('name', 'truncated_notes', 'items_count') Loading @@ -169,26 +275,32 @@ class StorageAdmin(admin.ModelAdmin): return obj.notes truncated_notes.short_description = 'notes' class LoanInline(admin.TabularInline): model = Loan class MemberLoanHistoryInline(LoanHistoryInline): verbose_name_plural = "Historique de prêt de matériel" class MemberCurrentLoanInline(CurrentLoanInline): verbose_name_plural = "Prêts de matériel en cours" class MemberAddLoanInline(AddLoanInline): extra = 0 exclude = ('notes',) readonly_fields = ('item', 'get_mac_and_serial', 'loan_date', 'loan_date_end', 'is_running') max_num = 10 verbose_name_plural = "Déclarer un prêt de matériel" show_change_link = True def get_queryset(self, request): qs = super(LoanInline, self).get_queryset(request) return qs.order_by('-loan_date_end') # Enrich the MemberAdmin with hardware-related information _MemberAdmin = admin.site._registry[coin.members.admin.Member].__class__ def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class MemberAdmin(_MemberAdmin): inlines = _MemberAdmin.inlines + [ MemberCurrentLoanInline, MemberAddLoanInline, MemberLoanHistoryInline, ] class MemberAdmin(coin.members.admin.MemberAdmin): inlines = coin.members.admin.MemberAdmin.inlines + [LoanInline] admin.site.unregister(coin.members.admin.Member) admin.site.register(coin.members.admin.Member, MemberAdmin) hardware_provisioning/autocomplete_light_registry.py 0 → 100644 +17 −0 Original line number Diff line number Diff line # -*- coding: utf-8 -*- import autocomplete_light from models import Item # This will generate a MemberAutocomplete class autocomplete_light.register(Item, # Just like in ModelAdmin.search_fields search_fields=[ 'designation', 'mac_address', 'serial'], attrs={ # This will set the input placeholder attribute: 'placeholder': "Nom / adresse MAC / n° de série de l'objet", 'data-autocomplete-minimum-characters': 3, }, ) hardware_provisioning/models.py +44 −2 Original line number Diff line number Diff line # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q from django.conf import settings Loading Loading @@ -74,6 +75,14 @@ class Item(models.Model): objects = ItemQuerySet().as_manager() def __unicode__(self): if self.serial and self.mac_address: return '{} ({} / {})'.format( self.designation, self.mac_address, self.serial ) elif self.serial or self.mac_address: return '{} ({})'.format( self.designation, self.mac_address or self.serial ) return self.designation def save(self, *args, **kwargs): Loading Loading @@ -102,6 +111,14 @@ class Item(models.Model): is_available.boolean = True is_available.short_description = 'disponible' def current_borrower(self): current_loan = self.loans.running().first() if current_loan: return current_loan.user else: return None current_borrower.short_description = 'Prêté à' def get_mac_and_serial(self): mac = self.mac_address serial = self.serial Loading @@ -109,6 +126,7 @@ class Item(models.Model): return "{} / {}".format(mac, serial) else: return mac or serial or '' get_mac_and_serial.short_description = 'Adresse MAC / n° de série' class Meta: verbose_name = 'objet' Loading Loading @@ -141,7 +159,9 @@ class Loan(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name='membre', related_name='loans', null=True, on_delete=models.SET_NULL) loan_date = models.DateTimeField(verbose_name='date de prêt') loan_date = models.DateTimeField( verbose_name='date de prêt', default=timezone.now) loan_date_end = models.DateTimeField(verbose_name='date de fin de prêt', null=True, blank=True) notes = models.TextField(null=True, blank=True) Loading @@ -152,9 +172,19 @@ class Loan(models.Model): def get_mac_and_serial(self): return self.item.get_mac_and_serial() get_mac_and_serial.short_description = "Adresse MAC / n° de série" def short_date(self): return '{:%d/%m/%y}'.format(self.loan_date) short_date.short_description = "Emprunté le…" def short_date_end(self): if self.loan_date_end: return '{:%d/%m/%y}'.format(self.loan_date_end) else: return None short_date_end.short_description = "Rendu le…" def user_can_close(self, user): return (not self.item.is_available()) and (self.user == user) Loading @@ -163,6 +193,18 @@ class Loan(models.Model): is_running.boolean = True is_running.short_description = 'En cours ?' def clean(self): current_loan = self.item.get_current_loan() if ( self.is_running() and current_loan and self.item.get_current_loan() != self ): raise ValidationError( "Il y a déjà un emprunt en cours sur cet objet") class Meta: verbose_name = 'prêt d’objet' verbose_name_plural = 'prêts d’objets' Loading hardware_provisioning/templates/hardware_provisioning/list.html +4 −8 Original line number Diff line number Diff line Loading @@ -22,24 +22,20 @@ <table id="member_loans" class="full-width"> <thead> <tr> <th>Type de matériel</th> <th>Matériel prêté</th> <th>Type de matériel</th> <th>Date de prêt</th> {% if view == 'old' %}<th>Date retour</th>{% endif %} <th>Addr. MAC</th> <th>Num. de série</th> {% if view != 'old' %}<th>Actions</th>{% endif %} </tr> </thead> <tbody> {% for loan in loans %} <tr> <td>{{ loan.item.type }}</td> <td>{{ loan.item }}</a></td> <td>{{ loan.loan_date }}</td> {% if view == 'old' %}<td>{{ loan.loan_date_end }}</td>{% endif %} <td>{{ loan.item.mac_address|default:"n/a" }}</td> <td>{{ loan.item.serial|default:"n/a" }}</td> <td>{{ loan.item.type }}</td> <td>{{ loan.loan_date|date:'j N Y' }}</td> {% if view == 'old' %}<td>{{ loan.loan_date_end|date:'j N Y' }}</td>{% endif %} {% if view != 'old' %} <td class="actions"> <div class="button-group"> Loading Loading
coin/settings_base.py +2 −3 Original line number Diff line number Diff line Loading @@ -166,7 +166,6 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: #'django.contrib.admindocs', 'polymorphic', # 'south', 'autocomplete_light', # Automagic autocomplete foreingkey form component 'activelink', # Detect if a link match actual page 'compat', Loading
hardware_provisioning/admin.py +182 −70 Original line number Diff line number Diff line Loading @@ -2,10 +2,12 @@ from __future__ import unicode_literals from django.contrib import admin from django.conf.urls import url from django.shortcuts import get_object_or_404 from django.contrib import admin, messages from django.contrib.auth import get_user_model from django.forms import ModelChoiceField from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect import autocomplete_light from .models import ItemType, Item, Loan, Storage Loading @@ -17,8 +19,47 @@ User = get_user_model() admin.site.register(ItemType) def _give_back_success_messages(items, request): for item in items: messages.success(request, "{} a bien été marqué comme rendu.".format(item)) if len(items) > 1: messages.warning( request, "N'oubliez pas de renseigner, pour chaque retour, le nouvel emprunteur " + "ou le nouveau lieu de stockage.", ) else: messages.warning( request, "N'oubliez pas de renseigner le nouvel emprunteur " + "ou le nouveau lieu de stockage.", ) def give_back_loan(request, id): # could be better : could not be a POST, and could not rely on HTTP_REFERER # We could for that rely on django-inline-actions, but the version # we could use with our Django 1.9 is outdated and buggy # We could also offer an intermediate page to specify the storage loan = get_object_or_404(Loan, pk=id) try: redirect_url = request.META['HTTP_REFERER'] except KeyError: # Fallback if no referer header is present redirect_url = reverse( 'admin:hardware_provisioning_item_change', args=[loan.item.id] ) loan.item.give_back() _give_back_success_messages([loan.item], request) return HttpResponseRedirect(redirect_url) class OwnerFilter(admin.SimpleListFilter): title = "Propriétaire" title = "propriétaire" parameter_name = 'owner' def lookups(self, request, model_admin): Loading Loading @@ -56,14 +97,113 @@ class AvailabilityFilter(admin.SimpleListFilter): return queryset class CurrentLoanInline(admin.TabularInline): model = Loan extra = 0 fields = ('user', 'item', 'short_date', 'notes', 'action_buttons') readonly_fields = ('user', 'item', 'short_date', 'notes', 'action_buttons') verbose_name_plural = "Prêt en cours" show_change_link = True def get_queryset(self, request): qs = super(CurrentLoanInline, self).get_queryset(request) return qs.running() def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False def action_buttons(self, obj): if obj.is_running(): return """<a class="button "href="{}">Déclarer rendu</a>""".format( reverse('admin:loan-give_back', args=[obj.pk])) else: return '' action_buttons.short_description = 'Actions' action_buttons.allow_tags = True class LoanHistoryInline(admin.TabularInline): model = Loan extra = 0 fields = ('user', 'item', 'short_date', 'short_date_end', 'notes') readonly_fields = ('user', 'item', 'short_date', 'short_date_end', 'notes') ordering = ['-loan_date_end'] verbose_name_plural = "Historique de prêt de cet objet" show_change_link = True classes = ['collapse'] # Django >= 1.10-ready def get_queryset(self, request): qs = super(LoanHistoryInline, self).get_queryset(request) return qs.finished() def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class AddLoanInline(admin.StackedInline): model = Loan extra = 1 max_num = 1 fields = ('user', 'item', 'loan_date', 'notes') verbose_name_plural = "Déclarer le prêt de cet objet" classes = ['collapse'] # Django >= 1.10-ready form = autocomplete_light.modelform_factory(Loan, fields='__all__') def get_queryset(self, request): qs = super(AddLoanInline, self).get_queryset(request) return qs.none() def has_delete_permission(self, request, obj=None): return False class BorrowerFilter(admin.SimpleListFilter): title = 'détenteur actuel' parameter_name = 'user' def _filter_loans(self, items_queryset, user_pk=None): qs = Loan.objects.running().filter(item__in=items_queryset) if user_pk is not None: qs.filter(user=user_pk) return qs def lookups(self, request, model_admin): # Get relevant (and authorized) users only relevant_items = model_admin.get_queryset(request) users = set() for loan in self._filter_loans(relevant_items): users.add((loan.user.pk, loan.user)) return users def queryset(self, request, queryset): if self.value(): loans_qs = self._filter_loans(queryset).filter( user__pk=self.value(), ) return queryset.filter(loans__in=loans_qs) else: return queryset @admin.register(Item) class ItemAdmin(admin.ModelAdmin): list_display = ( 'designation', 'mac_address', 'serial', 'owner', 'buy_date', 'deployed', 'is_available', 'storage') 'designation', 'current_borrower', 'get_mac_and_serial', 'deployed', 'is_available', 'storage', 'buy_date', 'owner', ) list_filter = ( AvailabilityFilter, 'type', 'storage', 'buy_date', OwnerFilter) 'buy_date', BorrowerFilter, OwnerFilter) search_fields = ( 'designation', 'mac_address', 'serial', 'owner__email', 'owner__nickname', Loading @@ -73,11 +213,25 @@ class ItemAdmin(admin.ModelAdmin): form = autocomplete_light.modelform_factory(Loan, fields='__all__') inlines = [AddLoanInline, CurrentLoanInline, LoanHistoryInline] def give_back(self, request, queryset): for item in queryset.filter(loans__loan_date_end=None): items = queryset.filter(loans__loan_date_end=None) for item in items: item.give_back() _give_back_success_messages(items, request) give_back.short_description = 'Rendre le matériel' def get_urls(self): urls = super(ItemAdmin, self).get_urls() my_urls = [ url( r'^give_back/(?P<id>.+)$', self.admin_site.admin_view(give_back_loan), name='loan-give_back'), ] return my_urls + urls class StatusFilter(admin.SimpleListFilter): title = 'Statut' Loading Loading @@ -110,54 +264,6 @@ class StatusFilter(admin.SimpleListFilter): return queryset class BorrowerFilter(admin.SimpleListFilter): title = 'Adhérent emprunteur' parameter_name = 'user' def lookups(self, request, model_admin): users = set() for loan in model_admin.get_queryset(request): users.add((loan.user.pk, loan.user)) return users def queryset(self, request, queryset): if self.value(): return queryset.filter(user=self.value()) else: return queryset class ItemChoiceField(ModelChoiceField): # On surcharge cette méthode pour afficher mac et n° de série dans le menu # déroulant de sélection d'un objet dans la création d'un prêt. def label_from_instance(self, obj): return obj.designation + ' ' + obj.get_mac_and_serial() @admin.register(Loan) class LoanAdmin(admin.ModelAdmin): list_display = ('item', 'get_mac_and_serial', 'user', 'loan_date', 'loan_date_end') list_filter = (StatusFilter, BorrowerFilter, 'item__designation') search_fields = ( 'item__designation', 'user__nickname', 'user__username', 'user__first_name', 'user__last_name', ) actions = ['end_loan'] def end_loan(self, request, queryset): queryset.filter(loan_date_end=None).update( loan_date_end=datetime.now()) end_loan.short_description = 'Mettre fin au prêt' form = autocomplete_light.modelform_factory(Loan, fields='__all__') def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'item': kwargs['queryset'] = Item.objects.all() return ItemChoiceField(**kwargs) else: return super(LoanAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) @admin.register(Storage) class StorageAdmin(admin.ModelAdmin): list_display = ('name', 'truncated_notes', 'items_count') Loading @@ -169,26 +275,32 @@ class StorageAdmin(admin.ModelAdmin): return obj.notes truncated_notes.short_description = 'notes' class LoanInline(admin.TabularInline): model = Loan class MemberLoanHistoryInline(LoanHistoryInline): verbose_name_plural = "Historique de prêt de matériel" class MemberCurrentLoanInline(CurrentLoanInline): verbose_name_plural = "Prêts de matériel en cours" class MemberAddLoanInline(AddLoanInline): extra = 0 exclude = ('notes',) readonly_fields = ('item', 'get_mac_and_serial', 'loan_date', 'loan_date_end', 'is_running') max_num = 10 verbose_name_plural = "Déclarer un prêt de matériel" show_change_link = True def get_queryset(self, request): qs = super(LoanInline, self).get_queryset(request) return qs.order_by('-loan_date_end') # Enrich the MemberAdmin with hardware-related information _MemberAdmin = admin.site._registry[coin.members.admin.Member].__class__ def has_add_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class MemberAdmin(_MemberAdmin): inlines = _MemberAdmin.inlines + [ MemberCurrentLoanInline, MemberAddLoanInline, MemberLoanHistoryInline, ] class MemberAdmin(coin.members.admin.MemberAdmin): inlines = coin.members.admin.MemberAdmin.inlines + [LoanInline] admin.site.unregister(coin.members.admin.Member) admin.site.register(coin.members.admin.Member, MemberAdmin)
hardware_provisioning/autocomplete_light_registry.py 0 → 100644 +17 −0 Original line number Diff line number Diff line # -*- coding: utf-8 -*- import autocomplete_light from models import Item # This will generate a MemberAutocomplete class autocomplete_light.register(Item, # Just like in ModelAdmin.search_fields search_fields=[ 'designation', 'mac_address', 'serial'], attrs={ # This will set the input placeholder attribute: 'placeholder': "Nom / adresse MAC / n° de série de l'objet", 'data-autocomplete-minimum-characters': 3, }, )
hardware_provisioning/models.py +44 −2 Original line number Diff line number Diff line # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q from django.conf import settings Loading Loading @@ -74,6 +75,14 @@ class Item(models.Model): objects = ItemQuerySet().as_manager() def __unicode__(self): if self.serial and self.mac_address: return '{} ({} / {})'.format( self.designation, self.mac_address, self.serial ) elif self.serial or self.mac_address: return '{} ({})'.format( self.designation, self.mac_address or self.serial ) return self.designation def save(self, *args, **kwargs): Loading Loading @@ -102,6 +111,14 @@ class Item(models.Model): is_available.boolean = True is_available.short_description = 'disponible' def current_borrower(self): current_loan = self.loans.running().first() if current_loan: return current_loan.user else: return None current_borrower.short_description = 'Prêté à' def get_mac_and_serial(self): mac = self.mac_address serial = self.serial Loading @@ -109,6 +126,7 @@ class Item(models.Model): return "{} / {}".format(mac, serial) else: return mac or serial or '' get_mac_and_serial.short_description = 'Adresse MAC / n° de série' class Meta: verbose_name = 'objet' Loading Loading @@ -141,7 +159,9 @@ class Loan(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name='membre', related_name='loans', null=True, on_delete=models.SET_NULL) loan_date = models.DateTimeField(verbose_name='date de prêt') loan_date = models.DateTimeField( verbose_name='date de prêt', default=timezone.now) loan_date_end = models.DateTimeField(verbose_name='date de fin de prêt', null=True, blank=True) notes = models.TextField(null=True, blank=True) Loading @@ -152,9 +172,19 @@ class Loan(models.Model): def get_mac_and_serial(self): return self.item.get_mac_and_serial() get_mac_and_serial.short_description = "Adresse MAC / n° de série" def short_date(self): return '{:%d/%m/%y}'.format(self.loan_date) short_date.short_description = "Emprunté le…" def short_date_end(self): if self.loan_date_end: return '{:%d/%m/%y}'.format(self.loan_date_end) else: return None short_date_end.short_description = "Rendu le…" def user_can_close(self, user): return (not self.item.is_available()) and (self.user == user) Loading @@ -163,6 +193,18 @@ class Loan(models.Model): is_running.boolean = True is_running.short_description = 'En cours ?' def clean(self): current_loan = self.item.get_current_loan() if ( self.is_running() and current_loan and self.item.get_current_loan() != self ): raise ValidationError( "Il y a déjà un emprunt en cours sur cet objet") class Meta: verbose_name = 'prêt d’objet' verbose_name_plural = 'prêts d’objets' Loading
hardware_provisioning/templates/hardware_provisioning/list.html +4 −8 Original line number Diff line number Diff line Loading @@ -22,24 +22,20 @@ <table id="member_loans" class="full-width"> <thead> <tr> <th>Type de matériel</th> <th>Matériel prêté</th> <th>Type de matériel</th> <th>Date de prêt</th> {% if view == 'old' %}<th>Date retour</th>{% endif %} <th>Addr. MAC</th> <th>Num. de série</th> {% if view != 'old' %}<th>Actions</th>{% endif %} </tr> </thead> <tbody> {% for loan in loans %} <tr> <td>{{ loan.item.type }}</td> <td>{{ loan.item }}</a></td> <td>{{ loan.loan_date }}</td> {% if view == 'old' %}<td>{{ loan.loan_date_end }}</td>{% endif %} <td>{{ loan.item.mac_address|default:"n/a" }}</td> <td>{{ loan.item.serial|default:"n/a" }}</td> <td>{{ loan.item.type }}</td> <td>{{ loan.loan_date|date:'j N Y' }}</td> {% if view == 'old' %}<td>{{ loan.loan_date_end|date:'j N Y' }}</td>{% endif %} {% if view != 'old' %} <td class="actions"> <div class="button-group"> Loading