8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-24 16:03:47 +00:00

Printer App a beginning.

This commit is contained in:
Maxime Bombar 2018-06-28 20:20:08 +02:00
parent d2a128326a
commit 3dc9957d4e
11 changed files with 416 additions and 0 deletions

37
printer/forms.py Normal file
View 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',
]

116
printer/models.py Normal file
View file

@ -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')

View 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 %}

View 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 %}

View 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 %}

17
printer/urls.py Normal file
View file

@ -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"),
]

72
printer/validators.py Normal file
View 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)

55
printer/views.py Normal file
View file

@ -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
)

View file

@ -75,6 +75,7 @@ LOCAL_APPS = (
're2o',
'preferences',
'logs',
'printer',
)
INSTALLED_APPS = (
DJANGO_CONTRIB_APPS +

View file

@ -73,6 +73,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:

View file

@ -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 %}