Esempio n. 1
0
    def on_model_change(self, form, model, is_created):
        if is_created:
            if bool(form.file.data) == form.no_file.data:
                raise ValidationError(
                    'Genau ein Feld von "Datei" oder "Keine Datei" muss angegeben werden.'
                )

        if model.id is None:
            sqla.session.flush()  # acquire ID

        if form.file.data:
            self._delete_document(model)  # delete old file
            save_file(model, form.file.data)

        if form.form_validated.data != model.validated:  # validation state changed
            if form.form_validated.data:
                model.validation_time = datetime.datetime.now()
            else:
                model.validation_time = None

        if model.validated and not model.has_barcode:
            if model.document_type != 'written':
                barcode.bake_barcode(model)
            config.document_validated(document_path(model.id))
            model.has_barcode = True  # this is the correct behaviour, although the has_barcode name does not indicate it

        super().on_model_change(form, model, is_created)
Esempio n. 2
0
    def on_model_change(self, form, model, is_created):
        if is_created:
            if bool(form.file.data) == form.no_file.data:
                raise ValidationError('Genau ein Feld von "Datei" oder "Keine Datei" muss angegeben werden.')
            model.validated = True

        if model.id is None:
            sqla.session.flush()  # acquire ID

        got_new_file = False
        if form.file.data:
            self._delete_document(model)  # delete old file
            save_file(model, form.file.data)
            if model.validated:
                got_new_file = True

        if model.validation_time is None and model.validated:
            # document has just been validated for the first time
            model.validation_time = datetime.datetime.now()
            got_new_file = model.has_file

        if got_new_file:
            if model.document_type != 'written':
                barcode.bake_barcode(model)
            config.document_validated(document_path(model.id))

        super().on_model_change(form, model, is_created)
Esempio n. 3
0
    def on_model_change(self, form, model, is_created):
        if is_created:
            if bool(form.file.data) == form.no_file.data:
                raise ValidationError('Genau ein Feld von "Datei" oder "Keine Datei" muss angegeben werden.')

        if model.id is None:
            sqla.session.flush()  # acquire ID

        if form.file.data:
            self._delete_document(model)  # delete old file
            save_file(model, form.file.data)

        if form.form_validated.data != model.validated:  # validation state changed
            if form.form_validated.data:
                model.validation_time = datetime.datetime.now()
            else:
                model.validation_time = None

        if model.validated and not model.has_barcode:
            if model.document_type != 'written':
                barcode.bake_barcode(model)
            config.document_validated(document_path(model.id))
            model.has_barcode = True  # this is the correct behaviour, although the has_barcode name does not indicate it

        super().on_model_change(form, model, is_created)
Esempio n. 4
0
 def _delete_document(self, model):
     if model.has_file:
         source = document_path(model.id)
         if os.path.exists(source):
             dest = os.path.join(config.DOCUMENT_DIRECTORY, 'trash', str(model.id))
             while os.path.exists(dest + '.pdf'):
                 dest += 'lol'
             os.renames(source, dest + '.pdf')
Esempio n. 5
0
def view_document(instance_id):
    if get_user() or is_kiosk():
        doc = Document.query.get(instance_id)
        if doc is None or not doc.has_file:
            raise ClientError('document not found', status=404)
        return send_file(document_path(doc.id))
    else:
        return unauthorized()
Esempio n. 6
0
def view_document(instance_id):
    if get_user() or is_kiosk():
        doc = Document.query.get(instance_id)
        if doc is None or not doc.has_file:
            raise ClientError('document not found', status=404)
        return send_file(document_path(doc.id))
    else:
        return unauthorized()
Esempio n. 7
0
 def _delete_document(self, model):
     if model.has_file:
         source = document_path(model.id)
         if os.path.exists(source):
             dest = os.path.join(config.DOCUMENT_DIRECTORY, 'trash',
                                 str(model.id))
             while os.path.exists(dest + '.pdf'):
                 dest += 'lol'
             os.renames(source, dest + '.pdf')
Esempio n. 8
0
def view_document(instance_id):
    doc = Document.query.get(instance_id)

    if get_user() or is_kiosk() or doc.publicly_available:
        if doc is None or not doc.has_file:
            raise ClientError('document not found', status=404)
        return send_file(document_path(doc.id), as_attachment=(request.args.get('download') is not None))
    else:
        return unauthorized()
Esempio n. 9
0
def submit_documents(validated):
    """Student document submission endpoint

    POST data must be multipart, with the `json` part conforming to
    DocumentLoadSchema and the `file` part being a pdf file.

    Uploaded files are stored in subdirectories below config.DOCUMENT_DIRECTORY.

    This method may raise AssertionErrors when the user sends invalid data.
    """
    # we can't use @deserialize because this endpoint uses multipart form data
    data = json.loads(request.form['json'])
    if get_user():
        (data, errors) = FullDocumentLoadSchema().load(data)
        if (data.get('document_type') == 'written' and data.get('solution') not in ['official', 'inofficial', 'none']
                or data.get('document_type') != 'written' and data.get('solution') is not None):
            errors['solution'] = 'Invalid value.'
    else:
        (data, errors) = DocumentLoadSchema().load(data)
    if errors:
        raise ClientError(*errors)
    assert 'file' in request.files
    file = request.files['file']
    if not _allowed_file(file.filename):
        raise ClientError('file extension not allowed', status=406)
    lectures = _match_lectures(data['lectures'], validated)
    examinants = _match_examinants(data['examinants'], validated)
    date = data['date']
    if not get_user():
        assert date <= datetime.date.today()
    new_doc = Document(
            department=data['department'],
            lectures=lectures,
            examinants=examinants,
            date=date,
            number_of_pages=0,  # will be filled in later or upon validation
            document_type=data['document_type'],
            validated=validated,
            validation_time=datetime.datetime.now() if validated else None,
            comment=data.get('comment'),
            solution=data.get('solution'),
            submitted_by=data.get('student_name'))
    sqla.session.add(new_doc)

    # we have the db side of things taken care of, now save the file
    # and tell the db where to find the file
    sqla.session.flush()  # necessary for id field to be populated
    save_file(new_doc, file)
    if validated:
        # we don't trust other people's PDFs...
        new_doc.number_of_pages = number_of_pages(new_doc)
    sqla.session.commit()
    app.logger.info("New document submitted (id: {})".format(new_doc.id))
    if validated:
        config.document_validated(document_path(new_doc.id))
    return {}
Esempio n. 10
0
def bake_barcode(document):
    """Put a generated barcode onto the PDF

    The pipeline works as follows:
    barcode.ps is piped into ps2pdf, followed by the call to the barcode-generating
    function. The resulting one-page PDF containing only the barcode is read into
    memory.
    This is then piped into pdftk, which grafts the barcode onto the first page
    of the document (discarding all other pages).
    We dump all but the first page of the document and concatenate our modified
    first page to it (via pdfjam).
    If this sounds somewhat roundabout, that's because it is.
    """

    # if the document has a legacy_id, the PDF already has a barcode.
    if document.legacy_id or not document.has_file:
        return
    doc_path = document_path(document.id)
    # The barcodes we use have 13 digits. The last one is a checksum digit. barcode.ps takes care of this.
    barcode = GS1_NAMESPACE + str(document.id).zfill(12 - len(GS1_NAMESPACE))
    with open(BARCODE_PS_FILE, 'rb') as barcode_file:
        ps2pdf = subprocess.Popen(['ps2pdf', '-', '-'],
                                  stdin=PIPE,
                                  stdout=PIPE)
        (barcode_pdf,
         _) = ps2pdf.communicate(barcode_file.read() + BARCODE_TEMPLATE.format(
             xpos=XPOS, ypos=YPOS, barcode=barcode).encode('utf-8'))
    assert ps2pdf.returncode == 0

    # pdftk outputs a pdf with only one page, discarding the rest of the PDF
    pdftk = subprocess.Popen(
        ['pdftk', '-', 'background', doc_path, 'output', '-'],
        stdin=PIPE,
        stdout=PIPE)
    (pdf_with_barcode, _) = pdftk.communicate(barcode_pdf)
    if document.number_of_pages > 1:
        rest_path = _tmp_path(document, '-rest.pdf')
        subprocess.check_call(
            ['pdfjam', '--nup', '1x1', doc_path, '2-', '--outfile', rest_path],
            stderr=DEVNULL)
        pdfjam = subprocess.Popen([
            'pdfjam', '--fitpaper', 'true', '/dev/stdin', rest_path,
            '--outfile', doc_path
        ],
                                  stdin=PIPE,
                                  stderr=DEVNULL)
        pdfjam.communicate(pdf_with_barcode)
        os.unlink(rest_path)
    else:
        with open(doc_path, 'wb') as document_file:
            document_file.write(pdf_with_barcode)
Esempio n. 11
0
    def print_all(self):
        folders = list(self._get_folders_with_counts())
        for folder, _ in folders:
            documents = self._query_unprinted(folder).all()
            config.print_documents(
                doc_paths=[document_path(doc.id) for doc in documents],
                cover_text=None,
                printer=config.FS_CONFIG['OFFICES'][self._get_location()]['printers'][0],
                usercode=config.PRINTER_USERCODES['internal'],
                job_title='Odie-Druck: Ordnerdruck'
            )
            folder.printed_docs += documents
            sqla.session.commit()

        return self.render('print_for_folder.html',
                           printed_folders=folders,
                           examinants_without_folders=Examinant.query.filter(~Examinant.folders.any()).sort_by(Examinant.name).all())
Esempio n. 12
0
def delete_document(id):
    doc = Document.query.get(id)
    if doc is None:
        raise ClientError('document not found')
    if doc.validated or (not doc.early_document_eligible and not doc.deposit_return_eligible):
        raise ClientError('document not eligible for deletion')

    sqla.session.delete(doc)

    if doc.has_file:
        source = document_path(doc.id)
        if os.path.exists(source):
            dest = os.path.join(config.DOCUMENT_DIRECTORY, 'trash', str(doc.id))
            while os.path.exists(dest + '.pdf'):
                dest += 'lol'
            os.renames(source, dest + '.pdf')

    sqla.session.commit()
    return {}
Esempio n. 13
0
def delete_document(id):
    doc = Document.query.get(id)
    if doc is None:
        raise ClientError('document not found')
    if doc.validated or (not doc.early_document_eligible and not doc.deposit_return_eligible):
        raise ClientError('document not eligible for deletion')

    sqla.session.delete(doc)

    if doc.has_file:
        source = document_path(doc.id)
        if os.path.exists(source):
            dest = os.path.join(config.DOCUMENT_DIRECTORY, 'trash', str(doc.id))
            while os.path.exists(dest + '.pdf'):
                dest += 'lol'
            os.renames(source, dest + '.pdf')

    sqla.session.commit()
    return {}
Esempio n. 14
0
    def print_all(self):
        folders = list(self._get_folders_with_counts())
        for folder, _ in folders:
            documents = self._query_unprinted(folder).all()
            config.print_documents(
                doc_paths=[document_path(doc.id) for doc in documents],
                cover_text=None,
                printer=config.FS_CONFIG['OFFICES'][
                    self._get_location()]['printers'][0],
                user=get_user().username,
                usercode=config.PRINTER_USERCODES['internal'],
                job_title='Odie-Druck: Ordnerdruck')
            folder.printed_docs += documents
            sqla.session.commit()

        return self.render('print_for_folder.html',
                           printed_folders=folders,
                           examinants_without_folders=Examinant.query.filter(
                               ~Examinant.folders.any()).sort_by(
                                   Examinant.name).all())
Esempio n. 15
0
def print_documents():
    # GET params could be too limited. therefore, cookies.
    try:
        data = PrintJobLoadSchema().loads(urllib.parse.unquote(request.cookies['print_data']))
    except marshmallow.exception.ValidationError as e:
        raise ClientError(str(e), status=500)
    document_ids = data['document_ids']
    app.logger.info("Printing document ids {} ({} in total) on {} for {}".format(document_ids, len(document_ids), data['printer'], data['cover_text']))

    document_objs = Document.query.filter(Document.id.in_(document_ids)).all()
    if any(not doc.has_file for doc in document_objs):
        raise ClientError('Tried to print at least one document without file', status=400)
    # sort the docs into the same order they came in the request
    docs_by_id = {doc.id: doc for doc in document_objs}
    documents = [docs_by_id[id] for id in document_ids]

    assert data['deposit_count'] >= 0
    print_price = sum(doc.price for doc in documents)
    # round up to next 10 cents
    print_price = 10 * (print_price//10 + (1 if print_price % 10 else 0))
    assert print_price + data['deposit_count'] * config.FS_CONFIG['DEPOSIT_PRICE'] == data['price']

    # pull current user out of app context
    user = get_user()
    yield None  # exit application context, start event stream

    num_pages = sum(doc.number_of_pages for doc in documents)

    try:
        if documents:
            db.accounting.log_exam_sale(num_pages, print_price, user, data['cash_box'])
        for _ in range(data['deposit_count']):
            lectures = Lecture.query.filter(Lecture.documents.any(Document.id.in_(document_ids))).all()
            dep = Deposit(
                    price=config.FS_CONFIG['DEPOSIT_PRICE'],
                    name=data['cover_text'],
                    by_user=user.full_name,
                    lectures=lectures)
            sqla.session.add(dep)
            db.accounting.log_deposit(dep, user, data['cash_box'])
            if documents:
                try:
                    name = data['cover_text'].split(' ')[0]
                    for _ in config.print_documents(
                        doc_paths=[document_path(doc.id) for doc in documents],
                        cover_text=data['cover_text'],
                        printer=data['printer'],
                        user=user.username,
                        usercode=config.PRINTER_USERCODES[data['cash_box']],
                        job_title="Odie-Druck für {} [{} Seiten]".format(name, num_pages)):
                            pass
                except Exception as e:
                    sqla.session.rollback()
                    raise NonConfidentialException('printing failed. Exception: ' + str(e)) from e
        sqla.session.commit()
        yield('accounting succeeded', '')
    except NonConfidentialException as e:
        raise NonConfidentialException(str(e)) from e
    except Exception as e:
        # in case of network troubles, we've just printed a set of documents but screwed up accounting.
        raise NonConfidentialException('accounting failed. Exception: ' + str(e)) from e
    yield ('complete', '')
Esempio n. 16
0
def submit_documents(validated):
    """Student document submission endpoint

    POST data must be multipart, with the `json` part conforming to
    DocumentLoadSchema and the `file` part being a pdf file.

    Uploaded files are stored in subdirectories below config.DOCUMENT_DIRECTORY.

    This method may raise AssertionErrors when the user sends invalid data.
    """
    # we can't use @deserialize because this endpoint uses multipart form data
    data = json.loads(request.form['json'])
    if get_user():
        try:
            data = FullDocumentLoadSchema().load(data)
        except marshmallow.exceptions.ValidationError as e:
            raise ClientError(str(e), status=500)
        if (data.get('document_type') == 'written' and data.get('solution')
                not in ['official', 'inofficial', 'none']
                or data.get('document_type') != 'written'
                and data.get('solution') is not None):
            raise ClientError('Invalid value.', status=400)
    else:
        try:
            data = DocumentLoadSchema().load(data)
        except marshmallow.exceptions.ValidationError as e:
            raise ClientError(str(e), status=400)
    assert 'file' in request.files
    file = request.files['file']
    if not _allowed_file(file.filename):
        raise ClientError('file extension not allowed', status=406)
    lectures = _match_lectures(data['lectures'], validated)
    examinants = _match_examinants(data['examinants'], validated)
    date = data['date']
    if not get_user():
        assert date <= datetime.date.today()

    doc_type = data.get('document_type')
    student_name = data.get('student_name')
    if student_name is None or student_name.isspace():
        student_name = None
    deposit_return_eligible = student_name is not None
    early_document_eligible = student_name is not None and doc_type == 'oral' and any(
        lecture.early_document_eligible for lecture in lectures)

    new_doc = Document(
        department=data['department'],
        lectures=lectures,
        examinants=examinants,
        date=date,
        number_of_pages=0,  # will be filled in later or upon validation
        document_type=doc_type,
        validation_time=datetime.datetime.now() if validated else None,
        comment=data.get('comment'),
        solution=data.get('solution'),
        submitted_by=student_name,
        early_document_eligible=early_document_eligible,
        deposit_return_eligible=deposit_return_eligible)
    sqla.session.add(new_doc)

    # we have the db side of things taken care of, now save the file
    # and tell the db where to find the file
    sqla.session.flush()  # necessary for id field to be populated
    save_file(new_doc, file)
    if validated:
        # we don't trust other people's PDFs...
        new_doc.number_of_pages = number_of_pages(new_doc)
    sqla.session.commit()
    app.logger.info("New document submitted (id: {})".format(new_doc.id))
    if validated:
        config.document_validated(document_path(new_doc.id))
    return {'early_document_eligible': early_document_eligible}