diff --git a/printer/forms.py b/printer/forms.py new file mode 100644 index 00000000..d013a91b --- /dev/null +++ b/printer/forms.py @@ -0,0 +1,37 @@ +# -*- mode: python; coding: utf-8 -*- + +"""printer.forms +Form to add, edit, cancel printer jobs. +Author : Maxime Bombar . +Date : 29/06/2018 +""" + +from django import forms +from django.forms import ( + Form, + ModelForm, +) + +import itertools + +from re2o.mixins import FormRevMixin + +from .models import ( + JobWithOptions, +) + + +class JobForm(FormRevMixin, ModelForm): + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + super(TrueJobForm, self).__init__(*args, prefix=prefix, **kwargs) + + class Meta: + model = JobWithOptions + fields = [ + 'file', + 'color', + 'disposition', + 'count', + ] + diff --git a/printer/models.py b/printer/models.py new file mode 100644 index 00000000..f16e620d --- /dev/null +++ b/printer/models.py @@ -0,0 +1,116 @@ +# -*- mode: python; coding: utf-8 -*- + +"""printer.models +Models of the printer application +Author : Maxime Bombar . +Date : 29/06/2018 +""" + +from __future__ import unicode_literals + +from django.db import models +from django.forms import ValidationError +from django.utils.translation import ugettext_lazy as _ +from django.template.defaultfilters import filesizeformat + +from re2o.mixins import RevMixin + +import users.models + +from .validators import ( + FileValidator, +) + +from .settings import ( + MAX_PRINTFILE_SIZE, + ALLOWED_TYPES, +) + + +""" +- ```user_printing_path``` is a function that returns the path of the uploaded file, used with the FileField. +- ```Job``` is the main model of a printer job. His parent is the ```user``` model. +""" + + +def user_printing_path(instance, filename): + # File will be uploaded to MEDIA_ROOT/printings/user_/ + return 'printings/user_{0}/{1}'.format(instance.user.id, filename) + + +class JobWithOptions(RevMixin, models.Model): + """ + This is the main model of printer application : + + - ```user``` is a ForeignKey to the User Application + - ```file``` is the file to print + - ```starttime``` is the time when the job was launched + - ```endtime``` is the time when the job was stopped. + A job is stopped when it is either finished or cancelled. + - ```status``` can be running, finished or cancelled. + - ```club``` is blank in general. If the job was launched as a club then + it is the id of the club. + - ```price``` is the total price of this printing. + + Printing Options : + + - ```format``` is the paper format. Example: A4. + - ```color``` is the colorization option. Either Color or Greyscale. + - ```disposition``` is the paper disposition. + - ```count``` is the number of copies to be printed. + - ```stapling``` is the stapling options. + - ```perforations``` is the perforation options. + + + Parent class : User + """ + STATUS_AVAILABLE = ( + ('Printable', 'Printable'), + ('Running', 'Running'), + ('Cancelled', 'Cancelled'), + ('Finished', 'Finished') + ) + user = models.ForeignKey('users.User', on_delete=models.PROTECT) + file = models.FileField(upload_to=user_printing_path, validators=[FileValidator(allowed_types=ALLOWED_TYPES, max_size=MAX_PRINTFILE_SIZE)]) + starttime = models.DateTimeField(auto_now_add=True) + endtime = models.DateTimeField(null=True) + status = models.CharField(max_length=255, choices=STATUS_AVAILABLE) + printAs = models.ForeignKey('users.User', on_delete=models.PROTECT, related_name='print_as_user', null=True) + price = models.IntegerField(default=0) + + FORMAT_AVAILABLE = ( + ('A4', 'A4'), + ('A3', 'A4'), + ) + COLOR_CHOICES = ( + ('Greyscale', 'Greyscale'), + ('Color', 'Color') + ) + DISPOSITIONS_AVAILABLE = ( + ('TwoSided', 'Two sided'), + ('OneSided', 'One sided'), + ('Booklet', 'Booklet') + ) + STAPLING_OPTIONS = ( + ('None', 'None'), + ('TopLeft', 'One top left'), + ('TopRight', 'One top right'), + ('LeftSided', 'Two left sided'), + ('RightSided', 'Two right sided') + ) + PERFORATION_OPTIONS = ( + ('None', 'None'), + ('TwoLeftSidedHoles', 'Two left sided holes'), + ('TwoRightSidedHoles', 'Two right sided holes'), + ('TwoTopHoles', 'Two top holes'), + ('TwoBottomHoles', 'Two bottom holes'), + ('FourLeftSidedHoles', 'Four left sided holes'), + ('FourRightSidedHoles', 'Four right sided holes') + ) + + format = models.CharField(max_length=255, choices=FORMAT_AVAILABLE, default='A4') + color = models.CharField(max_length=255, choices=COLOR_CHOICES, default='Greyscale') + disposition = models.CharField(max_length=255, choices=DISPOSITIONS_AVAILABLE, default='TwoSided') + count = models.PositiveIntegerField(default=1) + stapling = models.CharField(max_length=255, choices=STAPLING_OPTIONS, default='None') + perforation = models.CharField(max_length=255, choices=PERFORATION_OPTIONS, default='None') diff --git a/printer/templates/printer/echec.html b/printer/templates/printer/echec.html new file mode 100644 index 00000000..d15907c3 --- /dev/null +++ b/printer/templates/printer/echec.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load i18n %} + +{% load bootstrap3 %} +{% load massive_bootstrap_form %} +{% load static %} +{% block title %}Printing interface{% endblock %} + +{% block content %} +

{% trans "Failure" %}

+{% endblock %} diff --git a/printer/templates/printer/newjob.html b/printer/templates/printer/newjob.html new file mode 100644 index 00000000..1167e18e --- /dev/null +++ b/printer/templates/printer/newjob.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load i18n %} + +{% load bootstrap3 %} +{% load massive_bootstrap_form %} +{% load static %} +{% block title %}Printing interface{% endblock %} + +{% block content %} +
+ {% csrf_token %} +

{% trans "Printing Menu" %}

+ {{ jobform.management_form }} + {% bootstrap_formset_errors jobform %} +
+ {% for job in jobform.forms %} +
+ {% bootstrap_form job label_class='sr-only' %} + +
+ {% endfor %} +
+ + {% bootstrap_button action_name button_type="submit" icon="star" %} +
+ +{% endblock %} + diff --git a/printer/templates/printer/success.html b/printer/templates/printer/success.html new file mode 100644 index 00000000..c7649e0a --- /dev/null +++ b/printer/templates/printer/success.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load i18n %} + +{% load bootstrap3 %} +{% load massive_bootstrap_form %} +{% load static %} +{% block title %}Printing interface{% endblock %} + +{% block content %} +

{% trans "Success" %}

+{% endblock %} diff --git a/printer/urls.py b/printer/urls.py new file mode 100644 index 00000000..3629c741 --- /dev/null +++ b/printer/urls.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +"""printer.urls +The defined URLs for the printer app +Author : Maxime Bombar . +Date : 29/06/2018 +""" +from __future__ import unicode_literals + +from django.conf.urls import url + +import re2o +from . import views + +urlpatterns = [ + url(r'^new_job/$', views.new_job, name="new-job"), + url(r'^success/$', views.success, name="success"), +] diff --git a/printer/validators.py b/printer/validators.py new file mode 100644 index 00000000..069383cb --- /dev/null +++ b/printer/validators.py @@ -0,0 +1,72 @@ +# -*- mode: python; coding: utf-8 -*- + + +"""printer.validators +Custom validators useful for printer application. +Author : Maxime Bombar . +Date : 29/06/2018 +""" + + + +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError +from django.template.defaultfilters import filesizeformat +from django.utils.deconstruct import deconstructible + +import mimetypes + +@deconstructible +class FileValidator(object): + """ + Custom validator for files. It checks the size and mimetype. + + Parameters: + * ```allowed_types``` is an iterable of allowed mimetypes. Example: ['application/pdf'] for a pdf file. + * ```max_size``` is the maximum size allowed in bytes. Example: 25*1024*1024 for 25 MB. + + Usage example: + + class UploadModel(models.Model): + file = fileField(..., validators=FileValidator(allowed_types = ['application/pdf'], max_size=25*1024*1024)) + """ + + + def __init__(self, *args, **kwargs): + """ + Initialize the custom validator. + By default, all types and size are allowed. + """ + self.allowed_types = kwargs.pop('allowed_types', None) + self.max_size = kwargs.pop('max_size', None) + + def __call__(self, value): + """ + Check the type and size. + """ + + + type_message = _("MIME type '%(type)s' is not valid. Please, use one of these types: %(allowed_types)s.") + type_code = 'invalidType' + + oversized_message = _('The current file size is %(size)s. The maximum file size is %(max_size)s.') + oversized_code = 'oversized' + + + mimetype = mimetypes.guess_type(value.name)[0] + if self.allowed_types and not (mimetype in self.allowed_types): + type_params = { + 'type': mimetype, + 'allowed_types': ', '.join(self.allowed_types), + } + + raise ValidationError(type_message, code=type_code, params=type_params) + + filesize = len(value) + if self.max_size and filesize > self.max_size: + oversized_params = { + 'size': '{}'.format(filesizeformat(filesize)), + 'max_size': '{}'.format(filesizeformat(self.max_size)), + } + + raise ValidationError(oversized_message, code=oversized_code, params=oversized_params) diff --git a/printer/views.py b/printer/views.py new file mode 100644 index 00000000..4dced049 --- /dev/null +++ b/printer/views.py @@ -0,0 +1,55 @@ +# -*- mode: python; coding: utf-8 -*- + +"""printer.views +The views for the printer app +Author : Maxime Bombar . +Date : 29/06/2018 +""" + +from __future__ import unicode_literals + +from django.urls import reverse +from django.shortcuts import render, redirect +from django.forms import modelformset_factory, formset_factory + +from re2o.views import form +from users.models import User + +from . import settings + +from .forms import ( + JobForm, + ) + + +def new_job(request): + """ + View to create a new printing job + """ + job_formset = formset_factory(JobForm)( + request.POST or None, request.FILES, + ) + if job_formset.is_valid(): + for job in job_formset: + job = job.save(commit=False) + job.user=request.user + job.status='Printable' + job.save() + return redirect(reverse( + 'printer:success', + )) + return form( + { + 'jobform': job_formset, + 'action_name': "Print", + }, + 'printer/newjob.html', + request + ) + +def success(request): + return form( + {}, + 'printer/success.html', + request + ) diff --git a/re2o/settings.py b/re2o/settings.py index 0a019d2d..ec655844 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -77,6 +77,7 @@ LOCAL_APPS = ( 're2o', 'preferences', 'logs', + 'printer', ) INSTALLED_APPS = ( DJANGO_CONTRIB_APPS + diff --git a/re2o/urls.py b/re2o/urls.py index 39f51ec3..4ab790a4 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -74,6 +74,7 @@ urlpatterns = [ r'^preferences/', include('preferences.urls', namespace='preferences') ), + url(r'^printer/', include('printer.urls', namespace='printer')), ] # Add debug_toolbar URLs if activated if 'debug_toolbar' in settings.INSTALLED_APPS: