models.py 6.9 KB

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