payslip.views: 148 total statements, 100.0% covered

Generated: Tue 2013-05-07 14:07 CEST

Source file: /home/tobi/Projects/django-payslip/src/payslip/views.py

Stats: 135 executed, 0 missed, 13 excluded, 228 ignored

  1. """Views for the ``online_docs`` app."""
  2. import cStringIO as StringIO
  3. import os
  4. from django.contrib.auth.decorators import login_required
  5. from django.core.urlresolvers import reverse
  6. from django.db.models import Q, Sum
  7. from django.http import Http404, HttpResponse
  8. from django.utils.decorators import method_decorator
  9. from django.views.generic import (
  10. CreateView,
  11. DeleteView,
  12. FormView,
  13. TemplateView,
  14. UpdateView,
  15. )
  16. from dateutil import parser, rrule
  17. from xhtml2pdf import pisa
  18. from .app_settings import CURRENCY
  19. from .forms import (
  20. EmployeeForm,
  21. ExtraFieldForm,
  22. PaymentForm,
  23. PayslipForm,
  24. )
  25. from .models import (
  26. Company,
  27. Employee,
  28. ExtraField,
  29. ExtraFieldType,
  30. Payment,
  31. PaymentType,
  32. )
  33. #-------------#
  34. # Mixins #
  35. #-------------#
  36. class PermissionMixin(object):
  37. """Mixin to handle security functions."""
  38. @method_decorator(login_required)
  39. def dispatch(self, request, *args, **kwargs):
  40. """
  41. Makes sure that the user is logged in and has the right to display this
  42. view.
  43. """
  44. if not request.user.is_staff:
  45. raise Http404
  46. return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
  47. def get_success_url(self):
  48. return reverse('payslip_dashboard')
  49. class CompanyMixin(object):
  50. """Mixin to handle company related functions."""
  51. @method_decorator(login_required)
  52. def dispatch(self, request, *args, **kwargs):
  53. """
  54. Makes sure that the user is logged in and has the right to display this
  55. view.
  56. """
  57. self.kwargs = kwargs
  58. self.object = self.get_object()
  59. try:
  60. Employee.objects.get(company=self.object, user=request.user,
  61. is_manager=True)
  62. except Employee.DoesNotExist:
  63. if not request.user.is_staff:
  64. raise Http404
  65. return super(CompanyMixin, self).dispatch(request, *args, **kwargs)
  66. def get_success_url(self):
  67. return reverse('payslip_dashboard')
  68. class CompanyPermissionMixin(object):
  69. """Mixin to handle company-wide permissions functions."""
  70. @method_decorator(login_required)
  71. def dispatch(self, request, *args, **kwargs):
  72. """
  73. Makes sure that the user is logged in and has the right to display this
  74. view.
  75. """
  76. try:
  77. self.company = Employee.objects.get(
  78. user=request.user, is_manager=True).company
  79. except Employee.DoesNotExist:
  80. if not request.user.is_staff:
  81. raise Http404
  82. self.company = None
  83. return super(CompanyPermissionMixin, self).dispatch(request, *args,
  84. **kwargs)
  85. def get_success_url(self):
  86. return reverse('payslip_dashboard')
  87. class EmployeeMixin(object):
  88. """Mixin to handle employee related functions."""
  89. form_class = EmployeeForm
  90. def get_form_kwargs(self):
  91. kwargs = super(EmployeeMixin, self).get_form_kwargs()
  92. kwargs.update({'company': self.company})
  93. return kwargs
  94. class ExtraFieldMixin(object):
  95. """Mixin to handle extra field related functions."""
  96. model = ExtraField
  97. form_class = ExtraFieldForm
  98. class ExtraFieldTypeMixin(object):
  99. """Mixin to handle extra field type related functions."""
  100. model = ExtraFieldType
  101. class PaymentMixin(object):
  102. """Mixin to handle payment related functions."""
  103. model = Payment
  104. form_class = PaymentForm
  105. class PaymentTypeMixin(object):
  106. """Mixin to handle payment type related functions."""
  107. model = PaymentType
  108. #-------------#
  109. # Views #
  110. #-------------#
  111. class DashboardView(PermissionMixin, TemplateView):
  112. """Dashboard to navigate through the payslip app."""
  113. template_name = 'payslip/dashboard.html'
  114. def get_context_data(self, **kwargs):
  115. return {
  116. 'companies': Company.objects.all(),
  117. 'employees': Employee.objects.all(),
  118. 'extra_field_types': ExtraFieldType.objects.all(),
  119. 'fixed_value_extra_fields': ExtraField.objects.filter(
  120. field_type__fixed_values=True),
  121. 'payments': Payment.objects.all(),
  122. 'payment_types': PaymentType.objects.all(),
  123. }
  124. class CompanyCreateView(PermissionMixin, CreateView):
  125. """Classic view to create a company."""
  126. model = Company
  127. def get_success_url(self):
  128. return reverse('payslip_dashboard')
  129. class CompanyUpdateView(CompanyMixin, UpdateView):
  130. """Classic view to update a company."""
  131. model = Company
  132. class CompanyDeleteView(CompanyMixin, DeleteView):
  133. """Classic view to delete a company."""
  134. model = Company
  135. class EmployeeCreateView(CompanyPermissionMixin, EmployeeMixin, CreateView):
  136. """Classic view to create an employee."""
  137. model = Employee
  138. class EmployeeUpdateView(CompanyPermissionMixin, EmployeeMixin, UpdateView):
  139. """Classic view to update an employee."""
  140. model = Employee
  141. class EmployeeDeleteView(CompanyPermissionMixin, EmployeeMixin, DeleteView):
  142. """Classic view to delete an employee."""
  143. model = Employee
  144. class ExtraFieldTypeCreateView(PermissionMixin, ExtraFieldTypeMixin,
  145. CreateView):
  146. """Classic view to create an extra field type."""
  147. pass
  148. class ExtraFieldTypeUpdateView(PermissionMixin, ExtraFieldTypeMixin,
  149. UpdateView):
  150. """Classic view to update an extra field type."""
  151. pass
  152. class ExtraFieldTypeDeleteView(PermissionMixin, ExtraFieldTypeMixin,
  153. DeleteView):
  154. """Classic view to delete an extra field type."""
  155. pass
  156. class ExtraFieldCreateView(PermissionMixin, ExtraFieldMixin, CreateView):
  157. """Classic view to create an extra field."""
  158. pass
  159. class ExtraFieldUpdateView(PermissionMixin, ExtraFieldMixin, UpdateView):
  160. """Classic view to update an extra field."""
  161. pass
  162. class ExtraFieldDeleteView(PermissionMixin, ExtraFieldMixin, DeleteView):
  163. """Classic view to delete an extra field."""
  164. pass
  165. class PaymentTypeCreateView(CompanyPermissionMixin, PaymentTypeMixin,
  166. CreateView):
  167. """Classic view to create a payment type."""
  168. pass
  169. class PaymentTypeUpdateView(CompanyPermissionMixin, PaymentTypeMixin,
  170. UpdateView):
  171. """Classic view to update a payment type."""
  172. pass
  173. class PaymentTypeDeleteView(CompanyPermissionMixin, PaymentTypeMixin,
  174. DeleteView):
  175. """Classic view to delete a payment type."""
  176. pass
  177. class PaymentCreateView(CompanyPermissionMixin, PaymentMixin, CreateView):
  178. """Classic view to create a payment."""
  179. pass
  180. class PaymentUpdateView(CompanyPermissionMixin, PaymentMixin, UpdateView):
  181. """Classic view to update a payment."""
  182. pass
  183. class PaymentDeleteView(CompanyPermissionMixin, PaymentMixin, DeleteView):
  184. """Classic view to delete a payment."""
  185. pass
  186. class PayslipGeneratorView(CompanyPermissionMixin, FormView):
  187. """View to present a small form to generate a custom payslip."""
  188. template_name = 'payslip/payslip_form.html'
  189. form_class = PayslipForm
  190. def get_form_kwargs(self):
  191. kwargs = super(PayslipGeneratorView, self).get_form_kwargs()
  192. kwargs.update({'company': self.company})
  193. return kwargs
  194. def get_template_names(self):
  195. if hasattr(self, 'post_data'):
  196. return ['payslip/payslip.html']
  197. return super(PayslipGeneratorView, self).get_template_names()
  198. def get_context_data(self, **kwargs):
  199. kwargs = super(PayslipGeneratorView, self).get_context_data(**kwargs)
  200. if hasattr(self, 'post_data'):
  201. # Get form data
  202. employee = Employee.objects.get(pk=self.post_data.get('employee'))
  203. date_start = parser.parse(self.post_data.get('date_start'))
  204. date_end = parser.parse(self.post_data.get('date_end'))
  205. # Get payments for the selected year
  206. payments_year = employee.payments.filter(
  207. # Single payments in this year
  208. Q(date__year=date_start.year, payment_type__rrule__exact='') |
  209. # Recurring payments with past date and end_date in the
  210. # selected year or later
  211. Q(date__lte=date_end, end_date__gte=parser.parse(
  212. '{0}0101T000000'.format(date_start.year)),
  213. payment_type__rrule__isnull=False) |
  214. # Recurring payments with past date in period and open end
  215. Q(date__lte=date_end, end_date__isnull=True,
  216. payment_type__rrule__isnull=False)
  217. )
  218. # Get payments for the selected period
  219. payments = payments_year.filter(
  220. # Single payments in the selected period
  221. Q(date__gte=date_start, date__lte=date_end,
  222. payment_type__rrule__exact='') |
  223. # Recurring payments with past date and end_date in the period
  224. Q(end_date__lte=date_end, end_date__gte=date_start,
  225. date__lte=date_end, payment_type__rrule__isnull=False) |
  226. # Recurring payments with past date in period and open end
  227. Q(date__lte=date_end, end_date__isnull=True,
  228. payment_type__rrule__isnull=False)
  229. )
  230. # Yearly positive summary
  231. sum_year = payments_year.filter(
  232. amount__gt=0, payment_type__rrule__exact='').aggregate(
  233. Sum('amount')).get('amount__sum') or 0
  234. # Yearly negative summary
  235. sum_year_neg = payments_year.filter(
  236. amount__lt=0, payment_type__rrule__exact='').aggregate(
  237. Sum('amount')).get('amount__sum') or 0
  238. # Yearly summary of recurring payments
  239. for payment in payments_year.exclude(
  240. payment_type__rrule__exact=''):
  241. # If the recurring payment started in a year before, let's take
  242. # January 1st as a start, otherwise take the original date
  243. if payment.date.year < date_start.year:
  244. start = parser.parse('{0}0101T000000'.format(
  245. date_start.year))
  246. else:
  247. start = payment.date
  248. # If the payments ends before the period's end date, let's take
  249. # this date, otherwise we can take the period's end
  250. if payment.end_date and payment.end_date < date_end:
  251. end = payment.end_date
  252. else:
  253. end = date_end
  254. recurrings = rrule.rrule(
  255. rrule._rrulestr._freq_map.get(payment.payment_type.rrule),
  256. dtstart=start, until=end,
  257. )
  258. # Multiply amount with recurrings
  259. if payment.amount > 0:
  260. sum_year += payment.amount * recurrings.count()
  261. else:
  262. sum_year_neg += payment.amount * recurrings.count()
  263. # Period summaries
  264. sum = payments.filter(amount__gt=0).aggregate(
  265. Sum('amount')).get('amount__sum') or 0
  266. sum_neg = payments.filter(amount__lt=0).aggregate(
  267. Sum('amount')).get('amount__sum') or 0
  268. kwargs.update({
  269. 'employee': employee,
  270. 'date_start': date_start,
  271. 'date_end': date_end,
  272. 'payments': payments,
  273. 'payment_extra_fields': ExtraFieldType.objects.filter(
  274. model='Payment'),
  275. 'sum_year': sum_year,
  276. 'sum_year_neg': sum_year + sum_year_neg,
  277. 'sum': sum,
  278. 'sum_neg': sum_neg,
  279. 'currency': CURRENCY,
  280. })
  281. return kwargs
  282. def form_valid(self, form):
  283. self.post_data = self.request.POST
  284. if 'download' in self.post_data:
  285. result = StringIO.StringIO()
  286. html = self.render_to_response(self.get_context_data(form=form))
  287. f = open(os.path.join(
  288. os.path.dirname(__file__), './static/payslip/css/payslip.css'))
  289. pdf = pisa.CreatePDF(html.render().content, result,
  290. default_css=f.read())
  291. f.close()
  292. if not pdf.err:
  293. return HttpResponse(result.getvalue(),
  294. mimetype='application/pdf')
  295. return self.render_to_response(self.get_context_data(form=form))