mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-08-19 21:53:41 +00:00
Printer App a beginning.
This commit is contained in:
parent
9aa875113e
commit
767ba17fc3
11 changed files with 412 additions and 5 deletions
37
printer/forms.py
Normal file
37
printer/forms.py
Normal file
|
@ -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',
|
||||
]
|
||||
|
|
@ -1,3 +1,116 @@
|
|||
from django.db import models
|
||||
# -*- mode: python; coding: utf-8 -*-
|
||||
|
||||
# Create your models here.
|
||||
"""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')
|
||||
|
|
12
printer/templates/printer/echec.html
Normal file
12
printer/templates/printer/echec.html
Normal file
|
@ -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 %}
|
87
printer/templates/printer/newjob.html
Normal file
87
printer/templates/printer/newjob.html
Normal file
|
@ -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 %}
|
||||
|
12
printer/templates/printer/success.html
Normal file
12
printer/templates/printer/success.html
Normal file
|
@ -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 %}
|
|
@ -1,3 +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
|
||||
|
||||
urlpatterns = []
|
||||
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"),
|
||||
]
|
||||
|
|
72
printer/validators.py
Normal file
72
printer/validators.py
Normal file
|
@ -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)
|
|
@ -1,3 +1,55 @@
|
|||
from django.shortcuts import render
|
||||
# -*- mode: python; coding: utf-8 -*-
|
||||
|
||||
# Create your views here.
|
||||
"""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
|
||||
)
|
||||
|
|
|
@ -75,6 +75,7 @@ LOCAL_APPS = (
|
|||
're2o',
|
||||
'preferences',
|
||||
'logs',
|
||||
'printer',
|
||||
)
|
||||
INSTALLED_APPS = (
|
||||
DJANGO_CONTRIB_APPS +
|
||||
|
|
|
@ -71,6 +71,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:
|
||||
|
|
|
@ -112,6 +112,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</ul>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="glyphicon glyphicon-print"></i> Printer<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url "printer:new-job" %}"><i class="fa fa-print"></i> {% trans "Print" %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% can_view_app logs %}
|
||||
<li><a href="{% url "logs:index" %}"><i class="fa fa-chart-area"></i> {% trans "Statistics" %}</a></li>
|
||||
{% acl_end %}
|
||||
|
|
Loading…
Reference in a new issue