├── .gitignore ├── README.md ├── license.txt ├── pibicut ├── __init__.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── hooks.py ├── modules.txt ├── patches.txt ├── pibicut │ ├── __init__.py │ ├── custom.py │ └── doctype │ │ ├── __init__.py │ │ └── shortener │ │ ├── __init__.py │ │ ├── shortener.js │ │ ├── shortener.json │ │ ├── shortener.py │ │ ├── shortener_list.js │ │ ├── templates │ │ ├── shortener.html │ │ └── shortener_row.html │ │ └── test_shortener.py └── templates │ ├── __init__.py │ └── pages │ └── __init__.py └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | pibicut/docs/current -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pibicut 2 | 3 | PibiCut is a very Simple Frappe App to produce Shorten URLs on a Frappe Server and at the same time a QR Code to the short URL. 4 | 5 | ## License 6 | 7 | MIT# PibiCut 8 | 9 | ## Requirements 10 | Requires a Frappe server instance (refer to https://github.com/frappe/frappe), and has dependencies on QR Code (refer to https://github.com/lincolnloop/python-qrcode). 11 | 12 | ## Compatibility 13 | PibiCut has been tested on Frappe/ERPNext version-12, version-13 and version-14 as well. 14 | 15 | ## Installation 16 | From the frappe-bench folder, execute 17 | ``` 18 | $ bench get-app pibicut https://github.com/pibico/pibicut.git 19 | $ bench install-app pibicut 20 | ``` 21 | If you are using a multi-tenant environment, use the following command 22 | ``` 23 | $ bench --site site_name install-app pibicut 24 | ``` 25 | 26 | ## Update 27 | Run updates with 28 | ``` 29 | $ bench update 30 | ``` 31 | In case you update from the sources and observe errors, make sure to update dependencies with 32 | ``` 33 | $ bench update --requirements 34 | ``` 35 | 36 | ## Features 37 | Once installed, a new doctype 'Shortener' is generated. On searching the 'Shortener' Doctype you'll enter into the list of created shorten urls. You can create a new one, just click on New and enter the destination long URL. If you want, you can also insert a centered picture on the QR Code (the image must be in PNG format and white background, not transparent). 38 | 39 | As a result of saving the 'Shortener' Doctype, you will have a shorten url in the way http or https://site_name/MnOpQ. If you browse to this short url you will be redirected to the long url, the same is produced if you read the generated QR Code. Just try reading the QR Code in the following picture. 40 | 41 |  42 | 43 | ## Future Development 44 | Future improvements can be related to QR Code variations taking into consideration the libraries possibilities. Current Generated Short URL is considering random strings with 5 characters, but this can be changed directly in the code. Enjoy this very simple app! 45 | 46 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /pibicut/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | __version__ = '0.0.1' 5 | 6 | -------------------------------------------------------------------------------- /pibicut/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibicut/b1b77ce24c87d9e5e962005ac3daeaa78cb04e35/pibicut/config/__init__.py -------------------------------------------------------------------------------- /pibicut/config/desktop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from frappe import _ 4 | 5 | def get_data(): 6 | return [ 7 | { 8 | "module_name": "Pibicut", 9 | "color": "grey", 10 | "icon": "octicon octicon-file-directory", 11 | "type": "module", 12 | "label": _("Pibicut") 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /pibicut/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/pibicut" 6 | # docs_base_url = "https://[org_name].github.io/pibicut" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Pibicut" 12 | -------------------------------------------------------------------------------- /pibicut/hooks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from . import __version__ as app_version 4 | 5 | app_name = "pibicut" 6 | app_title = "Pibicut" 7 | app_publisher = "PibiCo" 8 | app_description = "Frappe/ERPNext URL Shortener" 9 | app_icon = "octicon octicon-file-directory" 10 | app_color = "grey" 11 | app_email = "pibico.sl@gmail.com" 12 | app_license = "MIT" 13 | 14 | # Includes in
15 | # ------------------ 16 | 17 | # include js, css files in header of desk.html 18 | # app_include_css = "/assets/pibicut/css/pibicut.css" 19 | # app_include_js = "/assets/pibicut/js/pibicut.js" 20 | 21 | # include js, css files in header of web template 22 | # web_include_css = "/assets/pibicut/css/pibicut.css" 23 | # web_include_js = "/assets/pibicut/js/pibicut.js" 24 | 25 | # include js in page 26 | # page_js = {"page" : "public/js/file.js"} 27 | 28 | # include js in doctype views 29 | # doctype_js = {"doctype" : "public/js/doctype.js"} 30 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 31 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 32 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 33 | 34 | # Home Pages 35 | # ---------- 36 | 37 | # application home page (will override Website Settings) 38 | # home_page = "login" 39 | 40 | # website user home page (by Role) 41 | # role_home_page = { 42 | # "Role": "home_page" 43 | # } 44 | 45 | # Website user home page (by function) 46 | # get_website_user_home_page = "pibicut.utils.get_home_page" 47 | 48 | # Generators 49 | # ---------- 50 | 51 | # automatically create page for each record of this doctype 52 | # website_generators = ["Web Page"] 53 | 54 | # Installation 55 | # ------------ 56 | 57 | # before_install = "pibicut.install.before_install" 58 | # after_install = "pibicut.install.after_install" 59 | 60 | # Desk Notifications 61 | # ------------------ 62 | # See frappe.core.notifications.get_notification_config 63 | 64 | # notification_config = "pibicut.notifications.get_notification_config" 65 | 66 | # Permissions 67 | # ----------- 68 | # Permissions evaluated in scripted ways 69 | 70 | # permission_query_conditions = { 71 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 72 | # } 73 | # 74 | # has_permission = { 75 | # "Event": "frappe.desk.doctype.event.event.has_permission", 76 | # } 77 | 78 | # Document Events 79 | # --------------- 80 | # Hook on document methods and events 81 | 82 | # doc_events = { 83 | # "*": { 84 | # "on_update": "method", 85 | # "on_cancel": "method", 86 | # "on_trash": "method" 87 | # } 88 | # } 89 | 90 | # Scheduled Tasks 91 | # --------------- 92 | 93 | # scheduler_events = { 94 | # "all": [ 95 | # "pibicut.tasks.all" 96 | # ], 97 | # "daily": [ 98 | # "pibicut.tasks.daily" 99 | # ], 100 | # "hourly": [ 101 | # "pibicut.tasks.hourly" 102 | # ], 103 | # "weekly": [ 104 | # "pibicut.tasks.weekly" 105 | # ] 106 | # "monthly": [ 107 | # "pibicut.tasks.monthly" 108 | # ] 109 | # } 110 | 111 | # Testing 112 | # ------- 113 | 114 | # before_tests = "pibicut.install.before_tests" 115 | 116 | # Overriding Methods 117 | # ------------------------------ 118 | # 119 | # override_whitelisted_methods = { 120 | # "frappe.desk.doctype.event.event.get_events": "pibicut.event.get_events" 121 | # } 122 | # 123 | # each overriding function accepts a `data` argument; 124 | # generated from the base implementation of the doctype dashboard, 125 | # along with any modifications made in other Frappe apps 126 | # override_doctype_dashboards = { 127 | # "Task": "pibicut.task.get_dashboard_data" 128 | # } 129 | 130 | -------------------------------------------------------------------------------- /pibicut/modules.txt: -------------------------------------------------------------------------------- 1 | Pibicut -------------------------------------------------------------------------------- /pibicut/patches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibicut/b1b77ce24c87d9e5e962005ac3daeaa78cb04e35/pibicut/patches.txt -------------------------------------------------------------------------------- /pibicut/pibicut/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibicut/b1b77ce24c87d9e5e962005ac3daeaa78cb04e35/pibicut/pibicut/__init__.py -------------------------------------------------------------------------------- /pibicut/pibicut/custom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2021, PibiCo and contributors 3 | # For license information, please see license.txt 4 | import frappe 5 | 6 | import qrcode 7 | from qrcode.image.styledpil import StyledPilImage 8 | from qrcode.image.styles.moduledrawers import SquareModuleDrawer, GappedSquareModuleDrawer, CircleModuleDrawer, RoundedModuleDrawer, VerticalBarsDrawer, HorizontalBarsDrawer 9 | from qrcode.image.styles.colormasks import RadialGradiantColorMask, SquareGradiantColorMask, HorizontalGradiantColorMask, VerticalGradiantColorMask, ImageColorMask 10 | 11 | from PIL import Image 12 | import base64, os 13 | from io import BytesIO 14 | 15 | def get_qrcode(input_data, logo): 16 | qr = qrcode.QRCode( 17 | version=7, 18 | box_size=6, 19 | border=3 20 | ) 21 | qr.add_data(input_data) 22 | qr.make(fit=True) 23 | 24 | if logo: 25 | img = qr.make_image(image_factory=StyledPilImage, color_mask=RadialGradiantColorMask(back_color = (255,255,255), center_color = (70,130,180), edge_color = (0,0,0)), module_drawer=GappedSquareModuleDrawer(), eye_drawer=SquareModuleDrawer(), embeded_image_path=logo) 26 | else: 27 | img = qr.make_image(image_factory=StyledPilImage, color_mask=RadialGradiantColorMask(back_color = (255,255,255), center_color = (70,130,180), edge_color = (0, 0, 0)), module_drawer=GappedSquareModuleDrawer(), eye_drawer=SquareModuleDrawer()) 28 | #qr = qrcode.make(input_str) 29 | temp = BytesIO() 30 | img.save(temp, "PNG") 31 | temp.seek(0) 32 | b64 = base64.b64encode(temp.read()) 33 | return "data:image/png;base64,{0}".format(b64.decode("utf-8")) 34 | -------------------------------------------------------------------------------- /pibicut/pibicut/doctype/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibicut/b1b77ce24c87d9e5e962005ac3daeaa78cb04e35/pibicut/pibicut/doctype/__init__.py -------------------------------------------------------------------------------- /pibicut/pibicut/doctype/shortener/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pibico/pibicut/b1b77ce24c87d9e5e962005ac3daeaa78cb04e35/pibicut/pibicut/doctype/shortener/__init__.py -------------------------------------------------------------------------------- /pibicut/pibicut/doctype/shortener/shortener.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, PibiCo and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('Shortener', { 5 | refresh(frm) { 6 | var template = ''; 7 | 8 | if (frm.doc.__islocal) { 9 | template = 'Redirecting to
{{ long_url }} 13 | 14 |