├── .bumpversion.cfg ├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── certificator ├── __init__.py ├── __main__.py ├── certificator.py ├── config.py ├── meetup │ ├── __init__.py │ ├── client.py │ └── models.py └── templates │ ├── styles.css │ └── template.html ├── examples ├── certificate.py ├── data.csv └── meta.json ├── pytest.ini ├── requirements-test.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py └── test_certificator.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [bumpversion:file:setup.py] 8 | 9 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | test: 5 | jobs: 6 | - py36 7 | - py37 8 | 9 | jobs: 10 | py36: &test-template 11 | working_directory: ~/repo 12 | docker: 13 | - image: circleci/python:3.6.8 14 | 15 | steps: 16 | - checkout 17 | 18 | - restore_cache: 19 | key: v1-dependencies-{{ checksum ".bumpversion.cfg" }}-{{ checksum "requirements.txt" }} 20 | 21 | - run: 22 | name: Install test dependencies 23 | command: | 24 | python3 -m venv venv 25 | . venv/bin/activate 26 | pip install -r requirements-test.txt 27 | pip install codecov 28 | 29 | - save_cache: 30 | paths: 31 | - ./venv 32 | key: v1-dependencies-{{ checksum ".bumpversion.cfg" }}-{{ checksum "requirements.txt" }} 33 | 34 | - run: 35 | name: Linters 36 | command: | 37 | . venv/bin/activate 38 | flake8 certificator tests --ignore=E501 39 | 40 | - run: 41 | name: Run tests 42 | command: | 43 | . venv/bin/activate 44 | pytest 45 | 46 | - run: 47 | name: codecov 48 | command: | 49 | . venv/bin/activate 50 | codecov 51 | 52 | py37: 53 | <<: *test-template 54 | docker: 55 | - image: circleci/python:3.7.3 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you need to exclude files such as those generated by an IDE, use 2 | # $GIT_DIR/info/exclude or the core.excludesFile configuration variable as 3 | # described in https://git-scm.com/docs/gitignore 4 | 5 | *.egg-info 6 | *.pot 7 | *.py[co] 8 | __pycache__ 9 | MANIFEST 10 | dist/ 11 | docs/_build/ 12 | docs/locale/ 13 | tests/coverage_html/ 14 | tests/.coverage 15 | build/ 16 | tests/report/ 17 | .env 18 | .cache 19 | .coverage 20 | *.pdf 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Luiz Menezes 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include certificator/templates/*.html 3 | include certificator/templates/*.css 4 | global-exclude *.*~ 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: clean-eggs clean-build 2 | @find . -iname '*.pyc' -delete 3 | @find . -iname '*.pyo' -delete 4 | @find . -iname '*~' -delete 5 | @find . -iname '*.swp' -delete 6 | @find . -iname '__pycache__' -delete 7 | 8 | clean-eggs: 9 | @find . -name '*.egg' -print0|xargs -0 rm -rf -- 10 | @rm -rf .eggs/ 11 | 12 | clean-build: 13 | @rm -fr build/ 14 | @rm -fr dist/ 15 | @rm -fr *.egg-info 16 | 17 | build: clean 18 | python setup.py sdist 19 | 20 | test: 21 | pytest -x 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Certificator 3 | ============ 4 | 5 | .. image:: https://badge.fury.io/py/certificator.svg 6 | :target: https://pypi.org/project/certificator/ 7 | 8 | .. image:: https://img.shields.io/badge/python-3.6,3.7-blue.svg 9 | :target: https://github.com/lamenezes/certificator/ 10 | 11 | .. image:: https://img.shields.io/github/license/lamenezes/certificator.svg 12 | :target: https://github.com/lamenezes/certificator/blob/master/LICENSE 13 | 14 | .. image:: https://circleci.com/gh/lamenezes/certificator.svg?style=shield 15 | :target: https://circleci.com/gh/lamenezes/certificator 16 | 17 | Event certificate generator. Currently supports CSV/JSON + Meetup. 18 | 19 | -------------- 20 | How to install 21 | -------------- 22 | 23 | :: 24 | 25 | pip install certificator 26 | 27 | 28 | ------------------------------------ 29 | How to use (using CLI interface) 30 | ------------------------------------ 31 | 32 | From local CSV file: 33 | 34 | .. code:: bash 35 | 36 | certificator csv \ 37 | --meta-file \ 38 | --data-file \ 39 | --template-path 40 | 41 | From MEETUP events: 42 | 43 | .. code:: bash 44 | 45 | certificator meetup \ 46 | --urlname \ 47 | --event-id \ 48 | --meetup-api-key \ 49 | --template-path 50 | 51 | For more options, type certificator --help 52 | 53 | 54 | ------------------------------------ 55 | Examples 56 | ------------------------------------ 57 | 58 | Inside the folder examples/ are some files to be used as examples. 59 | To generate certificates using the data in data.csv, you can run the command 60 | 61 | .. code:: bash 62 | 63 | certificator csv \ 64 | --meta-file meta.json \ 65 | --data-file data.csv 66 | -------------------------------------------------------------------------------- /certificator/__init__.py: -------------------------------------------------------------------------------- 1 | from .certificator import CSVCertificator 2 | from .meetup import MeetupCertificator 3 | 4 | 5 | __all__ = ('CSVCertificator', 'MeetupCertificator') 6 | -------------------------------------------------------------------------------- /certificator/__main__.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from . import config 4 | from .meetup import MeetupCertificator 5 | from .certificator import CSVCertificator 6 | 7 | 8 | @click.group() 9 | def main(): 10 | pass 11 | 12 | 13 | @main.command() 14 | @click.argument('destination', type=click.Path(exists=True, dir_okay=True), default='.') 15 | @click.option('--urlname', '-u') 16 | @click.option('--event-id', '-e') 17 | @click.option('--meetup-api-key', '-k', default=config.MEETUP_API_KEY, 18 | help="Your API key for Meetup") 19 | @click.option('--template-path', '-t', type=click.Path(exists=True, dir_okay=True), default=None, 20 | help="The path of your template.html file") 21 | @click.option('--filename-format', '-f', default='certificate-{id:0>3}.pdf', 22 | help="The file name format of the generated certificates") 23 | def meetup(destination, urlname, event_id, meetup_api_key, template_path, filename_format): 24 | """ 25 | Generate the certificates using your Meetup event 26 | """ 27 | 28 | assert urlname and event_id and meetup_api_key, ('You must pass the --urlname, --event_id ' 29 | 'and --meetup-api-key arguments') 30 | 31 | certifier = MeetupCertificator( 32 | urlname=urlname, 33 | event_id=event_id, 34 | destination_path=destination, 35 | template_path=template_path, 36 | api_key=meetup_api_key, 37 | filename_format=filename_format, 38 | ) 39 | certifier.generate() 40 | 41 | 42 | @main.command() 43 | @click.argument('destination', type=click.Path(exists=True, dir_okay=True), default='.') 44 | @click.option('--meta-file', '-m', prompt="Please enter your meta json file name", 45 | help="Your meta json file name") 46 | @click.option('--data-file', '-d', prompt="Please enter your data csv file name", 47 | help="Your data csv file name") 48 | @click.option('--template-path', '-t', type=click.Path(exists=True, dir_okay=True), default=None, 49 | help="The path of your template.html file") 50 | @click.option('--delimiter', default=',', 51 | help="The delimiter used in your data csv file. Example: --delimiter ';'") 52 | @click.option('--filename-format', '-f', default='certificate-{id:0>3}.pdf', 53 | help="The file name format of the generated certificates") 54 | def csv(destination, meta_file, data_file, template_path, delimiter, filename_format): 55 | """ 56 | Generate the certificates using local csv file 57 | """ 58 | 59 | certifier = CSVCertificator( 60 | delimiter=delimiter, 61 | meta_path=meta_file, 62 | data_path=data_file, 63 | template_path=template_path, 64 | destination_path=destination, 65 | filename_format=filename_format, 66 | ) 67 | certifier.generate() 68 | 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /certificator/certificator.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import json 3 | import os.path 4 | 5 | from jinja2 import Environment, FileSystemLoader, select_autoescape 6 | from weasyprint import HTML 7 | 8 | from . import config 9 | 10 | 11 | class BaseCertificator: 12 | def __init__(self, destination_path='.', template_path=None, template_filename='template.html', 13 | filename_format='certificate-{id:0>3}.pdf'): 14 | self.template_path = template_path 15 | self.destination_path = destination_path 16 | self.template_filename = template_filename 17 | self.filename_format = filename_format 18 | 19 | @property 20 | def meta(self): 21 | raise NotImplementedError() 22 | 23 | @property 24 | def certificate_data(self): 25 | raise NotImplementedError() 26 | 27 | @property 28 | def template_path(self): 29 | return self._template_path 30 | 31 | @template_path.setter 32 | def template_path(self, path): 33 | if not path: 34 | self._template_path = path 35 | return 36 | 37 | assert os.path.exists(path), 'You must provide an existing folder with the correct permissions' 38 | 39 | path = os.path.expanduser(path) 40 | self._template_path = path 41 | 42 | def get_template_paths(self): 43 | paths = [ 44 | os.path.abspath('.'), 45 | os.path.abspath('./templates'), 46 | config.TEMPLATES_PATH, 47 | ] 48 | 49 | if not self.template_path: 50 | return paths 51 | 52 | return [self.template_path] + paths 53 | 54 | @property 55 | def template(self): 56 | paths = self.get_template_paths() 57 | env = Environment( 58 | loader=FileSystemLoader(paths), 59 | autoescape=select_autoescape(['html', 'xml']), 60 | ) 61 | return env.get_template(self.template_filename) 62 | 63 | def get_context(self, **kwargs): 64 | context = {} 65 | 66 | context.update(self.meta) 67 | context.update(kwargs) 68 | 69 | return context 70 | 71 | def render(self, context): 72 | raw_html = self.template.render(**context) 73 | base_url = os.path.dirname(self.template.filename) 74 | return HTML(string=raw_html, base_url=base_url) 75 | 76 | def get_filepath(self, **kwargs): 77 | filename = self.filename_format.format(**kwargs) 78 | return os.path.join(self.destination_path, filename) 79 | 80 | def generate_one(self, context): 81 | html = self.render(context) 82 | filepath = self.get_filepath(**context) 83 | html.write_pdf(filepath) 84 | 85 | def generate(self): 86 | data = self.certificate_data 87 | for i, row in enumerate(data): 88 | context = self.get_context(id=i, **row) 89 | self.generate_one(context) 90 | 91 | 92 | class CSVCertificator(BaseCertificator): 93 | def __init__(self, delimiter=',', meta_path='./meta.json', data_path='./data.csv', **kwargs): 94 | super().__init__(**kwargs) 95 | self.delimiter = delimiter 96 | self.meta_path = meta_path 97 | self.data_path = data_path 98 | 99 | @property 100 | def meta(self): 101 | with open(self.meta_path) as f: 102 | return json.loads(f.read()) 103 | 104 | @property 105 | def certificate_data(self): 106 | with open(self.data_path) as f: 107 | return [row for row in csv.DictReader(f, delimiter=self.delimiter)] 108 | -------------------------------------------------------------------------------- /certificator/config.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from prettyconf import config 4 | 5 | PATH = os.path.abspath(os.path.dirname(__file__)) 6 | TEMPLATES_PATH = os.path.join(PATH, 'templates') 7 | MEETUP_API_KEY = config('MEETUP_API_KEY', default='') 8 | -------------------------------------------------------------------------------- /certificator/meetup/__init__.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime as dt 2 | 3 | from .client import MeetupClient 4 | from ..certificator import BaseCertificator 5 | from .models import Event 6 | 7 | 8 | class MeetupCertificator(BaseCertificator): 9 | def __init__(self, urlname, event_id, api_key, **kwargs): 10 | super().__init__(**kwargs) 11 | self.urlname = urlname 12 | self.event_id = event_id 13 | self.client = MeetupClient(api_key=api_key) 14 | 15 | @property 16 | def certificate_data(self): 17 | attendances = self.client.get_attendances(self.urlname, self.event_id) 18 | return ({'name': attendance['member']['name']} for attendance in attendances) 19 | 20 | @property 21 | def meta(self): 22 | event_data = self.client.get_event(self.urlname, self.event_id) 23 | event = Event(**event_data) 24 | event.clean() 25 | return { 26 | 'city': event.venue['city'], 27 | 'date': dt.strftime(event.date, '%d/%m/%Y'), 28 | 'full_date': event.full_date, 29 | 'organizer': event.group['name'], 30 | 'place': event.venue['name'], 31 | 'title': event.name, 32 | 'workload': event.duration, 33 | } 34 | -------------------------------------------------------------------------------- /certificator/meetup/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class MeetupClient: 5 | def __init__(self, api_key): 6 | self.params = {'key': api_key} 7 | 8 | def get_attendances(self, urlname, event_id): 9 | url = 'https://api.meetup.com/{}/events/{}/attendance'.format(urlname, event_id) 10 | response = requests.get(url, params=self.params) 11 | return response.json() 12 | 13 | def get_event(self, urlname, event_id): 14 | url = 'https://api.meetup.com/{}/events/{}'.format(urlname, event_id) 15 | response = requests.get(url, self.params) 16 | return response.json() 17 | 18 | def get_member(self, urlname, member_id): 19 | url = 'https://api.meetup.com/{}/members/{}'.format(urlname, member_id) 20 | response = requests.get(url, self.params) 21 | return response.json() 22 | -------------------------------------------------------------------------------- /certificator/meetup/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime as dt 2 | from simple_model import Model 3 | 4 | 5 | class Event(Model): 6 | fields = ( 7 | 'venue', 8 | 'group', 9 | 'duration', 10 | 'time', 11 | 'name', 12 | ) 13 | 14 | MONTHS = ( 15 | 'janeiro', 16 | 'fevereiro', 17 | 'março', 18 | 'abril', 19 | 'maio', 20 | 'junho', 21 | 'julho', 22 | 'agosto', 23 | 'setembro', 24 | 'outubro', 25 | 'novembro', 26 | 'dezembro', 27 | ) 28 | 29 | def clean_duration(self, value): 30 | if not value: 31 | return 'não especificado' 32 | 33 | return value / (60 * 60 * 1000) 34 | 35 | def clean_time(self, value): 36 | return value / 1000 37 | 38 | @property 39 | def date(self): 40 | return dt.fromtimestamp(self.time) 41 | 42 | @property 43 | def full_date(self): 44 | full_date = dt.strftime(self.date, '%d de {} de %Y') 45 | full_month = self.MONTHS[self.date.month - 1].title() 46 | return full_date.format(full_month) 47 | -------------------------------------------------------------------------------- /certificator/templates/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 25px; 3 | font-family: sans-serif; 4 | font-size: 18px; 5 | } 6 | 7 | @page{ 8 | size: A4 landscape; 9 | } 10 | 11 | #main { 12 | text-align: justify; 13 | margin-bottom: 50px; 14 | margin-left: 25px; 15 | margin-right: 25px; 16 | } 17 | 18 | #signature { 19 | border-top: 1px solid black; 20 | padding-top: 15px; 21 | text-align: center; 22 | } 23 | 24 | #top { 25 | text-align: center; 26 | } 27 | 28 | #top img { 29 | margin-top: -50px; 30 | margin-bottom: 25px; 31 | } 32 | 33 | #bot { 34 | font-size: 14px; 35 | margin-left: 30px; 36 | width: 100%; 37 | text-align: center; 38 | } 39 | 40 | h1 { 41 | text-align: center; 42 | margin-bottom: 50px; 43 | } 44 | -------------------------------------------------------------------------------- /certificator/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 |
10 |
11 |

Certificado

12 |

{{ organizer }} certifica que

13 |

{{ name }}

14 |

participou do(a) {{ title }}, promovido pelo(a) {{ organizer }}, que aconteceu no(a) {{ place }}, no dia {{ date }}, com carga horária total de {{ workload }}.

15 |
16 |
17 |
18 |

{{ city }}, {{ full_date }}

19 | 20 |
21 |

{{ responsible }}
{{ responsible_title }}

22 |
23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/certificate.py: -------------------------------------------------------------------------------- 1 | from certificator import CSVCertificator 2 | 3 | certificator = CSVCertificator(delimiter=';', filename_format='eventful-event-{name}.pdf') 4 | certificator.generate() 5 | -------------------------------------------------------------------------------- /examples/data.csv: -------------------------------------------------------------------------------- 1 | name;email 2 | John Doe;john@doe.com 3 | Jane Doe;jane@doe.com 4 | -------------------------------------------------------------------------------- /examples/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Eventful event for event-driven events", 3 | "organizer": "Eveorg", 4 | "place": "Event square", 5 | "date": "06/06/06", 6 | "full_date": "June 6, 2006", 7 | "city": "Eventville", 8 | "workload": "16 hours", 9 | "responsible": "Eve Ent Eventson", 10 | "responsible_title": "Event Organizer" 11 | } 12 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testspaths = tests 3 | addopts = -vv --cov=certificator --cov-report=term-missing 4 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | flake8 3 | pytest>=3.6 4 | pytest-cov 5 | faker 6 | json-encoder 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | jinja2 3 | prettyconf 4 | pysimplemodel 5 | requests 6 | weasyprint 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open('requirements.txt') as f: 4 | install_requires = f.readlines() 5 | 6 | setup( 7 | name='certificator', 8 | version='0.1.0', 9 | description='Certificate Generator Tool and Library', 10 | url='https://github.com/lamenezes/certificator', 11 | author='Luiz Menezes', 12 | author_email='luiz.menezesf@gmail.com', 13 | long_description=open('README.rst').read(), 14 | 15 | packages=find_packages(exclude=['tests*']), 16 | install_requires=install_requires, 17 | include_package_data=True, 18 | 19 | entry_points={ 20 | 'console_scripts': [ 21 | 'certificator=certificator.__main__:main', 22 | ] 23 | }, 24 | classifiers=[ 25 | 'Development Status :: 3 - Alpha', 26 | 'Environment :: Console', 27 | 'Intended Audience :: Developers', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.5', 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamenezes/certificator/fdb3d6f31499da8705de5453b8f55c0d313761bd/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_certificator.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import io 3 | import os 4 | from unittest import mock 5 | 6 | import pytest 7 | from faker import Faker 8 | from json_encoder import json 9 | 10 | from certificator.certificator import BaseCertificator, CSVCertificator 11 | 12 | faker = Faker() 13 | PATH = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | 16 | @pytest.fixture 17 | def certificator(): 18 | return BaseCertificator() 19 | 20 | 21 | @pytest.fixture() 22 | def fake_context(): 23 | return { 24 | 'city': faker.city(), 25 | 'date': faker.date(pattern='%d/%m/%Y'), 26 | 'full_date': faker.date(pattern='%d %B %Y'), 27 | 'organizer': faker.company(), 28 | 'title': faker.sentence(), 29 | 'workload': faker.random_int(min=4, max=300), 30 | } 31 | 32 | 33 | @pytest.fixture() 34 | def certificate_data(): 35 | return [{'name': faker.name(), 'company': faker.company()} for _ in range(5)] 36 | 37 | 38 | @pytest.fixture 39 | def mock_certificator(fake_context, certificate_data): 40 | class MockCertificator(BaseCertificator): 41 | @property 42 | def meta(self): 43 | return fake_context 44 | 45 | @property 46 | def certificate_data(self): 47 | return certificate_data 48 | 49 | @property 50 | def template(self): 51 | return mock.Mock() 52 | 53 | render = mock.Mock() 54 | 55 | return MockCertificator() 56 | 57 | 58 | @pytest.fixture 59 | def csv_certificator(): 60 | return CSVCertificator() 61 | 62 | 63 | @pytest.fixture 64 | def csv_certificate_file(certificate_data): 65 | stream = io.StringIO() 66 | writer = csv.DictWriter(stream, fieldnames=['name', 'company']) 67 | writer.writeheader() 68 | for row in certificate_data: 69 | writer.writerow(row) 70 | stream.seek(0) 71 | return stream 72 | 73 | 74 | @pytest.fixture 75 | def semicolon_csv_certificate_file(certificate_data): 76 | stream = io.StringIO() 77 | writer = csv.DictWriter(stream, fieldnames=['name', 'company'], 78 | delimiter=';') 79 | writer.writeheader() 80 | for row in certificate_data: 81 | writer.writerow(row) 82 | stream.seek(0) 83 | return stream 84 | 85 | 86 | def test_base_certificator_certificator(certificator): 87 | assert certificator.template_path is None 88 | assert certificator.destination_path 89 | with pytest.raises(NotImplementedError): 90 | certificator.meta 91 | with pytest.raises(NotImplementedError): 92 | certificator.certificate_data 93 | 94 | 95 | def test_base_certificator_template_path_set_invalid(certificator): 96 | path = 'foo/bar' 97 | with pytest.raises(AssertionError): 98 | certificator.template_path = path 99 | 100 | 101 | def test_base_certificator_template_path_set_valid(certificator): 102 | path = os.path.abspath(__file__) 103 | certificator.template_path = path 104 | 105 | assert certificator.template_path == path 106 | 107 | 108 | def test_base_certificator_get_template_paths(certificator): 109 | assert len(certificator.get_template_paths()) == 3 110 | 111 | 112 | def test_base_certificator_get_template_paths_custom_template_path(certificator): 113 | path = os.path.abspath(__file__) 114 | certificator.template_path = path 115 | 116 | paths = certificator.get_template_paths() 117 | assert len(paths) == 4 118 | assert path in paths 119 | 120 | 121 | def test_base_certificator_certificator_get_context(mock_certificator, fake_context): 122 | context = mock_certificator.get_context(foo='bar') 123 | 124 | assert context['foo'] == 'bar' 125 | assert not fake_context.keys() - context.keys() 126 | 127 | 128 | @mock.patch('certificator.certificator.FileSystemLoader') 129 | @mock.patch('certificator.certificator.Environment') 130 | def test_base_certificator_certificator_template(mock_env, mock_loader, certificator): 131 | assert certificator.template 132 | assert mock_loader.called 133 | assert mock_env.called 134 | assert mock_env.return_value.get_template.called 135 | 136 | 137 | @mock.patch('certificator.certificator.HTML') 138 | def test_base_certificator_render(mock_html, certificator, fake_context): 139 | with mock.patch('certificator.certificator.FileSystemLoader'), \ 140 | mock.patch('certificator.certificator.Environment') as mock_env: 141 | mock_env.return_value.get_template.return_value.filename = __file__ 142 | assert certificator.render(fake_context) 143 | assert mock_html.called_once_with(**fake_context) 144 | 145 | 146 | def test_base_certificator_get_filepath(mock_certificator): 147 | filepath = mock_certificator.get_filepath(id=10) 148 | assert '10' in filepath 149 | assert mock_certificator.destination_path in filepath 150 | 151 | 152 | def test_base_certificator_generate_one(mock_certificator, fake_context): 153 | fake_context['id'] = 1 154 | assert mock_certificator.generate_one(fake_context) is None 155 | 156 | assert mock_certificator.render.return_value.write_pdf.called 157 | 158 | 159 | def test_base_certificator_generate(mock_certificator, certificate_data): 160 | mock_certificator.generate_one = mock.Mock() 161 | 162 | mock_certificator.generate() 163 | 164 | assert mock_certificator.generate_one.called 165 | assert mock_certificator.generate_one.call_count == len(certificate_data) 166 | 167 | 168 | def test_csv_certificator(csv_certificator, fake_context): 169 | assert isinstance(csv_certificator, (BaseCertificator, CSVCertificator)) 170 | assert csv_certificator.delimiter 171 | assert csv_certificator.meta_path 172 | assert csv_certificator.data_path 173 | 174 | 175 | def test_csv_certificator_meta(csv_certificator, fake_context): 176 | with mock.patch('builtins.open', mock.mock_open(read_data=json.dumps(fake_context))): 177 | meta = csv_certificator.meta 178 | 179 | assert meta == fake_context 180 | 181 | 182 | def test_csv_certificator_certificate_data(csv_certificator, csv_certificate_file, certificate_data): 183 | with mock.patch('builtins.open', mock.Mock(return_value=csv_certificate_file)): 184 | data = csv_certificator.certificate_data 185 | 186 | assert data == certificate_data 187 | 188 | 189 | def test_csv_certificator_certificate_data_with_custom_delimiter(csv_certificator, semicolon_csv_certificate_file, certificate_data): 190 | csv_certificator.delimiter = ';' 191 | with mock.patch('builtins.open', mock.Mock(return_value=semicolon_csv_certificate_file)): 192 | data = csv_certificator.certificate_data 193 | 194 | assert data == certificate_data 195 | --------------------------------------------------------------------------------