From ed30ec5fe7e85b1fe58d9fa14a8eb35d1bcae6eb Mon Sep 17 00:00:00 2001 From: Tim Santor Date: Wed, 4 Sep 2024 11:33:33 -0400 Subject: [PATCH] update helpers --- {{cookiecutter.project_slug}}/helpers/ip.py | 57 ++++++++---------- {{cookiecutter.project_slug}}/helpers/qr.py | 59 +++++-------------- {{cookiecutter.project_slug}}/helpers/ua.py | 1 + .../mkdocs/mkdocs.yml | 2 +- 4 files changed, 40 insertions(+), 79 deletions(-) diff --git a/{{cookiecutter.project_slug}}/helpers/ip.py b/{{cookiecutter.project_slug}}/helpers/ip.py index 249b51ded6..b6807a60d1 100644 --- a/{{cookiecutter.project_slug}}/helpers/ip.py +++ b/{{cookiecutter.project_slug}}/helpers/ip.py @@ -1,8 +1,6 @@ import functools import ipaddress import logging -from abc import ABC -from abc import abstractmethod import requests from django.conf import settings @@ -13,14 +11,31 @@ logger = logging.getLogger(__name__) -class GeoLocationAPI(ABC): - @abstractmethod - def get_ip_info(self, ip_address: str) -> dict: - pass +@functools.lru_cache(maxsize=1024) +def get_ip_info(ip_address: str) -> dict: + """ + Use 3rd party API to obtain Geolocation data from a given IP. + Args: + ip_address (str): The IP address to get information about. -class IPStackAPI(GeoLocationAPI): - def get_ip_info(self, ip_address: str) -> dict: + Returns: + dict: A dictionary containing the information about the IP address, + or an empty dictionary if the IP address is internal or the API + response is not valid JSON. + """ + + if not settings.IPSTACK_ACCESS_KEY: + logger.warning("IPSTACK_ACCESS_KEY is not set.") + return {} + + merged_ips = list(set(settings.INTERNAL_IPS) | set(settings.IPSTACK_IGNORE_IPS)) + + if not ip_address or ip_address in merged_ips or is_private_ip(ip_address): + logger.warning("IP address is internal or ignored: %s", ip_address) + return {} + + try: params = { "fields": "main", "hostname": 1, @@ -36,32 +51,6 @@ def get_ip_info(self, ip_address: str) -> dict: return r.json() except JSONDecodeError: return {} - - -@functools.lru_cache(maxsize=1024) -def get_ip_info(ip_address: str, api: GeoLocationAPI) -> dict: - """ - Use 3rd party API to obtain Geolocation data from a given IP. - - Args: - ip_address (str): The IP address to get information about. - api (GeoLocationAPI): The API to use to get the IP info. - - Returns: - dict: A dictionary containing the information about the IP address, - or an empty dictionary if the IP address is internal or the API response - is not valid JSON. - """ - if ( - not ip_address - or ip_address in settings.INTERNAL_IPS - or is_private_ip(ip_address) - ): - logger.warning("IP address is internal: %s", ip_address) - return {} - - try: - return api.get_ip_info(ip_address) except Exception as e: # noqa: BLE001 logger.warning("Could not get IP info from API: %s", str(e)) return {} diff --git a/{{cookiecutter.project_slug}}/helpers/qr.py b/{{cookiecutter.project_slug}}/helpers/qr.py index 9b38fffec5..5c79e23f9a 100644 --- a/{{cookiecutter.project_slug}}/helpers/qr.py +++ b/{{cookiecutter.project_slug}}/helpers/qr.py @@ -1,65 +1,36 @@ +import base64 import logging from io import BytesIO import qrcode -from django.core.files.base import ContentFile -from qrcode.image.svg import SvgPathFillImage -from qrcode.main import QRCode -logger = logging.getLogger() +logger = logging.getLogger(__name__) -def make_qr_code(data: str, box_size: int = 10, border: int = 1) -> SvgPathFillImage: - """ - Generate a QR code, does not save the image. - - Args: - data (str): The data to be encoded in the QR code. - box_size (int, optional): The size of each box in the QR code grid. - Defaults to 10. - border (int, optional): The thickness of the border. Defaults to 1. - - Returns: - SvgPathFillImage: The generated QR code as an SVG image. - """ - qr = QRCode( +def make_qr_code(data, box_size=10, border=0): + """Generate a QR code, does not save the image.""" + qr = qrcode.QRCode( version=None, - error_correction=qrcode.constants.ERROR_CORRECT_H, + error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=box_size, border=border, - image_factory=SvgPathFillImage, ) qr.add_data(data) qr.make(fit=True) return qr.make_image(fill_color="black", back_color="white") -def _in_memory_qr(data: str) -> BytesIO: - """ - Save file to in-memory bytes buffer, not to disk and return the buffer. - - Args: - data (str): The data to be encoded in the QR code. - - Returns: - BytesIO: The in-memory bytes buffer containing the QR code. - """ +def make_in_memory_qr(data): + """Save file to in-memory bytes buffer, not to disk and return the buffer.""" img = make_qr_code(data) bytes_io = BytesIO() - img.save(bytes_io) + img.save(bytes_io, format="PNG") return bytes_io -def make_qr_content_file(data: str, filename: str) -> ContentFile: - """ - Return content file for use in Django. - - Args: - data (str): The data to be encoded in the QR code. - filename (str): The name of the file. - - Returns: - ContentFile: The content file containing the QR code. - """ - bytes_io = _in_memory_qr(data) - return ContentFile(bytes_io.getvalue(), name=f"{filename}") +def make_qr_code_b64(data): + """Return a Base64 encoded QR code for use in HTML. NOTE: Base64 encoded + images are NOT well supported in email clients.""" + bytes_io = make_in_memory_qr(data) + img_str = base64.b64encode(bytes_io.getvalue()).decode() + return f"data:image/png;base64, {img_str}" diff --git a/{{cookiecutter.project_slug}}/helpers/ua.py b/{{cookiecutter.project_slug}}/helpers/ua.py index 6629c5ba66..62cde94bad 100644 --- a/{{cookiecutter.project_slug}}/helpers/ua.py +++ b/{{cookiecutter.project_slug}}/helpers/ua.py @@ -17,6 +17,7 @@ def get_ua_platform(user_agent: UserAgent) -> str: def get_ua(request: HttpRequest) -> str: + """Return User Agent string.""" return request.headers.get("user-agent", "") diff --git a/{{cookiecutter.project_slug}}/mkdocs/mkdocs.yml b/{{cookiecutter.project_slug}}/mkdocs/mkdocs.yml index 2492531c69..184b6c29d4 100644 --- a/{{cookiecutter.project_slug}}/mkdocs/mkdocs.yml +++ b/{{cookiecutter.project_slug}}/mkdocs/mkdocs.yml @@ -67,7 +67,7 @@ markdown_extensions: - pymdownx.tilde - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_sv + emoji_generator: !!python/name:material.extensions.emoji.to_svg # Plugins plugins: