mirror of https://gitlab.federez.net/re2o/re2o
10 changed files with 410 additions and 0 deletions
@ -0,0 +1,37 @@ |
|||||
|
# -*- mode: python; coding: utf-8 -*- |
||||
|
|
||||
|
"""printer.forms |
||||
|
Form to add, edit, cancel printer jobs. |
||||
|
Author : Maxime Bombar <bombar@crans.org>. |
||||
|
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', |
||||
|
] |
||||
|
|
||||
@ -0,0 +1,116 @@ |
|||||
|
# -*- mode: python; coding: utf-8 -*- |
||||
|
|
||||
|
"""printer.models |
||||
|
Models of the printer application |
||||
|
Author : Maxime Bombar <bombar@crans.org>. |
||||
|
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_<id>/<filename> |
||||
|
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') |
||||
@ -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 %} |
||||
|
<h3>{% trans "Failure" %}</h3> |
||||
|
{% endblock %} |
||||
@ -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 %} |
||||
|
<form class="form" method="post" enctype="multipart/form-data"> |
||||
|
{% csrf_token %} |
||||
|
<h3>{% trans "Printing Menu" %}</h3> |
||||
|
{{ jobform.management_form }} |
||||
|
{% bootstrap_formset_errors jobform %} |
||||
|
<div id="form_set" class="form-group"> |
||||
|
{% for job in jobform.forms %} |
||||
|
<div class='file_to_print form-inline'> |
||||
|
{% bootstrap_form job label_class='sr-only' %} |
||||
|
<button class="btn btn-danger btn-sm" id="id_form-0-job-remove" type="button"> |
||||
|
<span class="fa fa-times"></span> |
||||
|
</button> |
||||
|
</div> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add a file"%}" id="add_one"> |
||||
|
{% bootstrap_button action_name button_type="submit" icon="star" %} |
||||
|
</form> |
||||
|
<script type="text/javascript"> |
||||
|
|
||||
|
var template = `{% bootstrap_form jobform.empty_form label_class='sr-only' %} |
||||
|
<button class="btn btn-danger btn-sm" |
||||
|
id="id_form-__prefix__-job-remove" type="button"> |
||||
|
<span class="fa fa-times"></span> |
||||
|
</button>` |
||||
|
|
||||
|
function add_job() { |
||||
|
var new_index = |
||||
|
document.getElementsByClassName('file_to_print').length; |
||||
|
document.getElementById('id_form-TOTAL_FORMS').value ++; |
||||
|
var new_job = document.createElement('div'); |
||||
|
new_job.className = 'file_to_print form-inline'; |
||||
|
new_job.innerHTML = template.replace(/__prefix__/g, new_index); |
||||
|
document.getElementById('form_set').appendChild(new_job); |
||||
|
add_listener_for_id(new_index); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function del_job(event){ |
||||
|
var job = event.target.parentNode; |
||||
|
job.parentNode.removeChild(job); |
||||
|
document.getElementById('id_form-TOTAL_FORMS').value --; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function add_listener_for_id(i){ |
||||
|
document.getElementById('id_form-' + i.toString() + '-job-remove') |
||||
|
.addEventListener("click", function(event){ |
||||
|
var job = event.target.parentNode; |
||||
|
job.parentNode.removeChild(job); |
||||
|
document.getElementById('id_form-TOTAL_FORMS').value --; |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// Add events manager when DOM is fully loaded |
||||
|
document.addEventListener( |
||||
|
"DOMContentLoaded", |
||||
|
function() { |
||||
|
document.getElementById("add_one") |
||||
|
.addEventListener("click", add_job, true); |
||||
|
document.getElementById('id_form-0-job-remove') |
||||
|
.addEventListener("click", function(event){ |
||||
|
var job = event.target.parentNode; |
||||
|
job.parentNode.removeChild(job); |
||||
|
document.getElementById('id_form-TOTAL_FORMS').value --; |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
); |
||||
|
|
||||
|
</script> |
||||
|
{% endblock %} |
||||
|
|
||||
@ -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 %} |
||||
|
<h3>{% trans "Success" %}</h3> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,17 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
"""printer.urls |
||||
|
The defined URLs for the printer app |
||||
|
Author : Maxime Bombar <bombar@crans.org>. |
||||
|
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"), |
||||
|
] |
||||
@ -0,0 +1,72 @@ |
|||||
|
# -*- mode: python; coding: utf-8 -*- |
||||
|
|
||||
|
|
||||
|
"""printer.validators |
||||
|
Custom validators useful for printer application. |
||||
|
Author : Maxime Bombar <bombar@crans.org>. |
||||
|
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) |
||||
@ -0,0 +1,55 @@ |
|||||
|
# -*- mode: python; coding: utf-8 -*- |
||||
|
|
||||
|
"""printer.views |
||||
|
The views for the printer app |
||||
|
Author : Maxime Bombar <bombar@crans.org>. |
||||
|
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 |
||||
|
) |
||||
Loading…
Reference in new issue