models.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.core.exceptions import ValidationError
  4. from django.db import models
  5. from django.db.models import Q
  6. from django.conf import settings
  7. from django.utils import timezone
  8. from .fields import MACAddressField
  9. class ItemType(models.Model):
  10. name = models.CharField(max_length=100, verbose_name='nom')
  11. def __unicode__(self):
  12. return self.name
  13. class Meta:
  14. verbose_name = 'type d’objet'
  15. verbose_name_plural = 'types d’objet'
  16. class ItemQuerySet(models.QuerySet):
  17. def _get_borrowed_pks(self):
  18. return Loan.objects.running().values_list('item', flat=True)
  19. def available(self):
  20. return self.exclude(
  21. pk__in=self._get_borrowed_pks()).exclude(deployed=True)
  22. def borrowed(self):
  23. return self.filter(pk__in=self._get_borrowed_pks())
  24. def deployed(self):
  25. return self.filter(deployed=True)
  26. def unavailable(self):
  27. """ deployed or borrowed
  28. """
  29. return self.filter(
  30. Q(pk__in=self._get_borrowed_pks()) |
  31. Q(deployed=True))
  32. class Item(models.Model):
  33. type = models.ForeignKey(ItemType, verbose_name='type de matériel',
  34. related_name='items')
  35. designation = models.CharField(max_length=100, verbose_name='désignation')
  36. storage = models.ForeignKey(
  37. 'Storage', related_name='items',
  38. verbose_name='Lieu de stockage',
  39. null=True, blank=True,
  40. help_text='Laisser vide si inconnu')
  41. mac_address = MACAddressField(
  42. verbose_name='adresse MAC',
  43. blank=True, null=True, unique=True,
  44. help_text="préférable au n° de série si possible")
  45. serial = models.CharField(
  46. verbose_name='N° de série',
  47. max_length=250, blank=True, null=True, unique=True,
  48. help_text='ou toute autre référence unique')
  49. buy_date = models.DateField(verbose_name='date d’achat' , blank=True , null=True)
  50. owner = models.ForeignKey(
  51. settings.AUTH_USER_MODEL,
  52. verbose_name='Propriétaire',
  53. related_name='items',
  54. null=True, blank=True,
  55. help_text="dans le cas de matériel n'appartenant pas à l'association")
  56. deployed = models.BooleanField(verbose_name='déployé', default=False,
  57. help_text='Cocher si le matériel est en production')
  58. comment = models.TextField(verbose_name='commentaire', blank=True,
  59. null=True)
  60. objects = ItemQuerySet().as_manager()
  61. def __unicode__(self):
  62. return self.designation
  63. def save(self, *args, **kwargs):
  64. # workaround for unique=True, null=True
  65. # see https://code.djangoproject.com/ticket/4136#comment:33
  66. self.mac_address = self.mac_address or None
  67. self.serial = self.serial or None
  68. return super(Item, self).save(*args, **kwargs)
  69. def get_current_loan(self):
  70. """
  71. Returns the current Loan for this Item, if exists, or None.
  72. """
  73. try:
  74. return self.loans.get(loan_date_end__isnull=True)
  75. except Loan.DoesNotExist:
  76. return None
  77. def is_available(self):
  78. """
  79. Returns the status of the Item. If a running loan exists,
  80. or if the item is deployed, returns False (else True).
  81. """
  82. return (not self.deployed) and (not self.loans.running().exists())
  83. is_available.boolean = True
  84. is_available.short_description = 'disponible'
  85. def current_borrower(self):
  86. current_loan = self.loans.running().first()
  87. if current_loan:
  88. return current_loan.user
  89. else:
  90. return None
  91. current_borrower.short_description = 'Prêté à'
  92. def get_mac_and_serial(self):
  93. mac = self.mac_address
  94. serial = self.serial
  95. if mac and serial:
  96. return "{} / {}".format(mac, serial)
  97. else:
  98. return mac or serial or ''
  99. get_mac_and_serial.short_description = 'Adresse MAC / n° de série'
  100. class Meta:
  101. verbose_name = 'objet'
  102. ordering = ['designation', 'mac_address', 'serial']
  103. def give_back(self, storage=None):
  104. self.storage = storage
  105. self.save()
  106. self.loans.running().update(
  107. loan_date_end=timezone.now())
  108. class LoanQuerySet(models.QuerySet):
  109. @staticmethod
  110. def _running_filter():
  111. return (
  112. models.Q(loan_date_end__gt=timezone.now()) |
  113. models.Q(loan_date_end__isnull=True))
  114. def running(self):
  115. return self.filter(self._running_filter())
  116. def finished(self):
  117. return self.exclude(self._running_filter())
  118. class Loan(models.Model):
  119. item = models.ForeignKey(Item, verbose_name='objet', related_name='loans')
  120. user = models.ForeignKey(
  121. settings.AUTH_USER_MODEL, verbose_name='membre',
  122. related_name='loans', null=True, on_delete=models.SET_NULL)
  123. loan_date = models.DateTimeField(verbose_name='date de prêt')
  124. loan_date_end = models.DateTimeField(verbose_name='date de fin de prêt',
  125. null=True, blank=True)
  126. notes = models.TextField(null=True, blank=True)
  127. def __unicode__(self):
  128. return 'prêt de {item} à {user}'.format(
  129. item=self.item, user=self.user)
  130. def get_mac_and_serial(self):
  131. return self.item.get_mac_and_serial()
  132. get_mac_and_serial.short_description = "Adresse MAC / n° de série"
  133. def user_can_close(self, user):
  134. return (not self.item.is_available()) and (self.user == user)
  135. def is_running(self):
  136. return not self.loan_date_end or self.loan_date_end > timezone.now()
  137. is_running.boolean = True
  138. is_running.short_description = 'En cours ?'
  139. def clean(self):
  140. current_loan = self.item.get_current_loan()
  141. if (
  142. self.is_running()
  143. and
  144. current_loan
  145. and
  146. self.item.get_current_loan() != self
  147. ):
  148. raise ValidationError(
  149. "Il y a déjà un emprunt en cours sur cet objet")
  150. class Meta:
  151. verbose_name = 'prêt d’objet'
  152. verbose_name_plural = 'prêts d’objets'
  153. objects = LoanQuerySet().as_manager()
  154. class Storage(models.Model):
  155. name = models.CharField(max_length=100, verbose_name='nom')
  156. notes = models.TextField(
  157. blank=True,
  158. help_text='Lisible par tous les adhérents')
  159. def __unicode__(self):
  160. return self.name
  161. def items_count(self):
  162. return self.items.count()
  163. items_count.short_description = 'Nb. items stockés'
  164. class Meta:
  165. verbose_name = 'lieu de stockage'
  166. verbose_name_plural = 'lieux de stockage'