diff --git a/sage_qrcode/models/barcode.py b/sage_qrcode/models/barcode.py index 03274d2..c6dc5a9 100644 --- a/sage_qrcode/models/barcode.py +++ b/sage_qrcode/models/barcode.py @@ -11,6 +11,7 @@ class Barcode(PolymorphicModel, TimeStampMixin): """Abstract base class for all QR code types.""" bar_code_image = models.ImageField( + verbose_name=_("Bar Code Image"), upload_to="bar_codes/", blank=True, null=True, @@ -20,6 +21,7 @@ class Barcode(PolymorphicModel, TimeStampMixin): ) title = models.CharField( + verbose_name=_("Title"), max_length=255, null=True, blank=True, @@ -28,17 +30,21 @@ class Barcode(PolymorphicModel, TimeStampMixin): ) color = ColorField( + verbose_name=_("Color"), format="hex", - help_text=_("Color of the BAR code."), + default="#000000", null=True, blank=True, + help_text=_("Color of the BAR code."), db_comment="The color of the BAR code in hexadecimal format.", ) second_color = ColorField( + verbose_name=_("Second Color"), format="hex", - help_text=_("Second color of the QR code."), + default="##FFFFFF", null=True, blank=True, + help_text=_("Second color of the QR code."), db_comment="The second color of the BAR code in hexadecimal format.", ) @@ -63,9 +69,9 @@ class BarcodeUrl(Barcode): """ url = models.URLField( + verbose_name=_("barcode URL"), help_text=_("URL of the barcode content."), db_comment="The URL of the barcode content.", - verbose_name=_("barcode URL"), ) def __str__(self): @@ -90,10 +96,10 @@ class BarcodeText(Barcode): """ body = models.TextField( + verbose_name=_("body"), blank=True, help_text=_("Body of the barcode."), db_comment="The body of the barcode.", - verbose_name=_("body"), ) def __str__(self): diff --git a/sage_qrcode/models/bitcoin.py b/sage_qrcode/models/bitcoin.py index 2a9df0a..20f53b0 100644 --- a/sage_qrcode/models/bitcoin.py +++ b/sage_qrcode/models/bitcoin.py @@ -15,13 +15,14 @@ class BitcoinQRCode(QRCode): """ bitcoin_address = models.CharField( + verbose_name=_("Bitcoin Address"), max_length=34, validators=[validate_bitcoin_address], help_text=_("Bitcoin address. Example: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'"), db_comment="The Bitcoin address for the payment.", - verbose_name=_("Bitcoin Address"), ) amount = models.DecimalField( + verbose_name=_("Amount"), max_digits=10, decimal_places=8, null=False, @@ -29,20 +30,19 @@ class BitcoinQRCode(QRCode): validators=[MinValueValidator(0.00000001)], help_text=_("Amount of Bitcoin to send. Example: '0.01'"), db_comment="The amount of Bitcoin to send.", - verbose_name=_("Amount"), ) label = models.CharField( + verbose_name=_("Label"), max_length=255, blank=True, help_text=_("Label for the transaction."), db_comment="An optional label for the transaction.", - verbose_name=_("Label"), ) message = models.TextField( + verbose_name=_("Message"), blank=True, help_text=_("Message for the transaction."), db_comment="An optional message for the transaction.", - verbose_name=_("Message"), ) def __str__(self): diff --git a/sage_qrcode/models/card.py b/sage_qrcode/models/card.py index f7e9ccd..0ad8ddc 100644 --- a/sage_qrcode/models/card.py +++ b/sage_qrcode/models/card.py @@ -15,56 +15,56 @@ class VCardQRCode(QRCode): """ full_name = models.CharField( + verbose_name=_("Full Name"), max_length=255, help_text=_("Full name of the individual."), db_comment="The name of the individual represented in the VCard QR code.", - verbose_name=_("Full Name"), ) display_name = models.CharField( + verbose_name=_("Display Name"), max_length=255, null=True, blank=True, help_text=_("Display name of the individual."), db_comment="An optional display name for the individual.", - verbose_name=_("Display Name"), ) email = models.EmailField( + verbose_name=_("Email"), null=True, blank=True, help_text=_("Email address of the individual."), db_comment="The email address of the individual.", - verbose_name=_("Email"), ) phone = models.CharField( + verbose_name=_("Phone Number"), max_length=20, null=True, blank=True, validators=[validate_phone_number], help_text=_("Phone number of the individual."), db_comment="The phone number of the individual.", - verbose_name=_("Phone Number"), ) url = models.URLField( + verbose_name=_("Website URL"), null=True, blank=True, help_text=_("URL of the individual's website or profile."), db_comment="The URL to the individual's website or profile.", - verbose_name=_("Website URL"), ) org = models.CharField( + verbose_name=_("Organization"), max_length=255, null=True, blank=True, help_text=_("Name of the organization."), db_comment="The organization name associated with the individual.", - verbose_name=_("Organization"), ) address = models.TextField( + verbose_name=_("Address"), null=True, blank=True, help_text=_("Physical address of the individual."), db_comment="The physical address of the individual.", - verbose_name=_("Address"), ) phone_number = models.CharField( _("Phone Number"), diff --git a/sage_qrcode/models/epc.py b/sage_qrcode/models/epc.py index 7876a7e..f9d4438 100644 --- a/sage_qrcode/models/epc.py +++ b/sage_qrcode/models/epc.py @@ -15,31 +15,31 @@ class EPCQRCode(QRCode): """ name = models.CharField( + verbose_name=_("Beneficiary Name"), max_length=255, help_text=_("Name of the EPC beneficiary."), db_comment="The name of the beneficiary for the EPC QR code.", - verbose_name=_("Beneficiary Name"), ) iban = models.CharField( + verbose_name=_("IBAN"), max_length=34, validators=[validate_iban], help_text=_("IBAN of the EPC beneficiary."), db_comment="The IBAN of the beneficiary for the EPC QR code.", - verbose_name=_("IBAN"), ) amount = models.DecimalField( + verbose_name=_("Payment Amount"), max_digits=10, decimal_places=2, validators=[MinValueValidator(0.01)], help_text=_("Payment amount."), db_comment="The amount to be paid using the EPC QR code.", - verbose_name=_("Payment Amount"), ) text = models.TextField( + verbose_name=_("Additional Text"), blank=True, help_text=_("Additional text for the EPC QR code."), db_comment="Optional additional text for the EPC QR code.", - verbose_name=_("Additional Text"), ) def __str__(self): diff --git a/sage_qrcode/models/qrcode.py b/sage_qrcode/models/qrcode.py index 6e64012..ee1f359 100644 --- a/sage_qrcode/models/qrcode.py +++ b/sage_qrcode/models/qrcode.py @@ -11,6 +11,7 @@ class QRCode(PolymorphicModel, TimeStampMixin): """Abstract base class for all QR code types.""" qr_code_image = models.ImageField( + verbose_name=_("QR Code Image"), upload_to="qr_codes/", blank=True, null=True, @@ -19,6 +20,7 @@ class QRCode(PolymorphicModel, TimeStampMixin): db_comment="The image file of the generated QR code.", ) custom_gif = models.ImageField( + verbose_name=_("Custom GIF"), upload_to="custom_gifs/", blank=True, null=True, @@ -27,6 +29,7 @@ class QRCode(PolymorphicModel, TimeStampMixin): db_comment="An optional custom GIF that can be embedded in the QR code.", ) title = models.CharField( + verbose_name=_("Title"), max_length=255, null=True, blank=True, @@ -34,32 +37,36 @@ class QRCode(PolymorphicModel, TimeStampMixin): db_comment="A descriptive title for the QR code.", ) size = models.PositiveSmallIntegerField( + verbose_name=_("Size"), validators=[validate_size], - help_text=_("Size of the QR code image."), null=True, blank=True, + help_text=_("Size of the QR code image."), db_comment="The size (dimensions) of the QR code image.", ) color = ColorField( + verbose_name=_("Color"), format="hex", - help_text=_("Color of the QR code."), null=True, blank=True, + help_text=_("Color of the QR code."), db_comment="The color of the QR code in hexadecimal format.", ) second_color = ColorField( + verbose_name=_("Second Color"), format="hex", - help_text=_("Second color of the QR code."), null=True, blank=True, + help_text=_("Second color of the QR code."), db_comment="The second color of the QR code in hexadecimal format.", ) third_color = ColorField( + verbose_name=_("Third Color"), format="hex", - help_text=_("Third color of the QR code."), null=True, blank=True, + help_text=_("Third color of the QR code."), db_comment="The third color of the BAR code in hexadecimal format.", ) diff --git a/sage_qrcode/models/social_media.py b/sage_qrcode/models/social_media.py index 8aec5c1..63056dd 100644 --- a/sage_qrcode/models/social_media.py +++ b/sage_qrcode/models/social_media.py @@ -23,17 +23,17 @@ class WhatsAppQRCode(QRCode): """ phone_number = models.CharField( + verbose_name=_("WhatsApp Phone Number"), max_length=20, validators=[validate_phone_number], help_text=_("WhatsApp phone number. Example: +1234567890"), db_comment="The WhatsApp phone number.", - verbose_name=_("WhatsApp Phone Number"), ) message = models.TextField( + verbose_name=_("Message"), blank=True, help_text=_("Message to be sent. Example: 'Hello, this is a test message.'"), db_comment="The message to be sent via WhatsApp.", - verbose_name=_("Message"), ) def __str__(self): @@ -54,12 +54,12 @@ class Meta: class SkypeQRCode(QRCode): url = models.URLField( + verbose_name=_("Skype URL"), + validators=[validate_skype], help_text=_( "URL of the Skype profile. Example: 'https://www.skype.com/username'" ), - validators=[validate_skype], db_comment="The URL of the Skype profile.", - verbose_name=_("Skype URL"), ) def __str__(self): @@ -84,12 +84,12 @@ class TikTokQRCode(QRCode): """ url = models.URLField( + verbose_name=_("TikTok URL"), + validators=[validate_tiktok], help_text=_( "URL of the TikTok profile. Example: 'https://www.tiktok.com/@username'" ), - validators=[validate_tiktok], db_comment="The URL of the TikTok profile.", - verbose_name=_("TikTok URL"), ) def __str__(self): @@ -114,12 +114,12 @@ class SnapchatQRCode(QRCode): """ url = models.URLField( + verbose_name=_("Snapchat URL"), + validators=[validate_snapchat], help_text=_( "URL of the Snapchat profile. Example: 'https://www.snapchat.com/add/username'" ), - validators=[validate_snapchat], db_comment="The URL of the Snapchat profile.", - verbose_name=_("Snapchat URL"), ) def __str__(self): @@ -144,12 +144,12 @@ class InstagramQRCode(QRCode): """ url = models.URLField( + verbose_name=_("Instagram URL"), + validators=[validate_instagram], help_text=_( "URL of the Instagram profile. Example: 'https://www.instagram.com/username'" ), - validators=[validate_instagram], db_comment="The URL of the Instagram profile.", - verbose_name=_("Instagram URL"), ) def __str__(self): @@ -174,12 +174,12 @@ class FacebookQRCode(QRCode): """ url = models.URLField( + verbose_name=_("Facebook URL"), + validators=[validate_facebook], help_text=_( "URL of the Facebook profile. Example: 'https://www.facebook.com/username'" ), - validators=[validate_facebook], db_comment="The URL of the Facebook profile.", - verbose_name=_("Facebook URL"), ) def __str__(self): @@ -204,10 +204,10 @@ class TelegramQRCode(QRCode): """ url = models.URLField( - help_text=_("URL of the Telegram profile. Example: 'https://t.me/username'"), + verbose_name=_("Telegram URL"), validators=[validate_telegram], + help_text=_("URL of the Telegram profile. Example: 'https://t.me/username'"), db_comment="The URL of the Telegram profile.", - verbose_name=_("Telegram URL"), ) def __str__(self): @@ -232,12 +232,12 @@ class LinkedInQRCode(QRCode): """ url = models.URLField( + verbose_name=_("LinkedIn URL"), + validators=[validate_linkedin], help_text=_( "URL of the LinkedIn profile. Example: 'https://www.linkedin.com/in/username'" ), - validators=[validate_linkedin], db_comment="The URL of the LinkedIn profile.", - verbose_name=_("LinkedIn URL"), ) def __str__(self): @@ -261,13 +261,13 @@ class XQRCode(QRCode): scanned, it directs the user to the Twitter profile page. """ + verbose_name=_("Twitter URL"), url = models.URLField( + validators=[validate_x], help_text=_( "URL of the X profile. Example: 'https://www.twitter.com/username'" ), - validators=[validate_x], db_comment="The URL of the Twitter profile.", - verbose_name=_("Twitter URL"), ) def __str__(self): @@ -292,9 +292,9 @@ class MediaUrl(QRCode): """ url = models.URLField( + verbose_name=_("Media URL"), help_text=_("URL of the media content."), db_comment="The URL of the media content.", - verbose_name=_("Media URL"), ) def __str__(self): diff --git a/sage_qrcode/models/wifi.py b/sage_qrcode/models/wifi.py index d9fd015..8d81c1c 100644 --- a/sage_qrcode/models/wifi.py +++ b/sage_qrcode/models/wifi.py @@ -12,23 +12,23 @@ class WifiQRCode(QRCode): """ ssid = models.CharField( + verbose_name=_("SSID"), max_length=255, help_text=_("SSID of the WiFi network."), db_comment="The SSID of the WiFi network.", - verbose_name=_("SSID"), ) password = models.CharField( + verbose_name=_("WiFi Password"), max_length=255, help_text=_("Password of the WiFi network."), db_comment="The password for the WiFi network.", - verbose_name=_("WiFi Password"), ) security = models.CharField( + verbose_name=_("Security Type"), max_length=50, default="WPA", help_text=_("Security type of the WiFi network."), db_comment="The security type for the WiFi network (e.g., WPA, WPA2).", - verbose_name=_("Security Type"), ) def __str__(self): diff --git a/sage_qrcode/tests/service/test_barcode.py b/sage_qrcode/tests/service/test_barcode.py index 21eb7ec..b9f38bf 100644 --- a/sage_qrcode/tests/service/test_barcode.py +++ b/sage_qrcode/tests/service/test_barcode.py @@ -1,8 +1,9 @@ -from unittest.mock import patch, MagicMock -from PIL import Image from io import BytesIO +from unittest.mock import MagicMock, patch + import pytest from django.core.exceptions import ValidationError +from PIL import Image class TestBarcodeProxy: @@ -55,23 +56,22 @@ def test_generate_barcode_text( @patch("pyshorteners.Shortener") def test_shorten_url(self, mock_shortener, barcode_proxy, sample_url): - mock_tinyurl = mock_shortener.return_value.tinyurl - mock_tinyurl.short.return_value = "https://tinyurl.com/shortened-url" + mock_tiny_url = mock_shortener.return_value.tinyurl + mock_tiny_url.short.return_value = "https://tinyurl.com/shortened-url" shortened_url = barcode_proxy.shorten_url(sample_url) assert shortened_url == "https://tinyurl.com/shortened-url" - mock_tinyurl.short.assert_called_once_with(sample_url) + mock_tiny_url.short.assert_called_once_with(sample_url) @patch("sage_qrcode.service.barcode.get_barcode_class") - def test_save_barcode( - self, mock_get_barcode_class, barcode_proxy, sample_text, tmp_path + def test_save_barcode_no_disk_write( + self, mock_get_barcode_class, barcode_proxy, sample_text ): mock_barcode_instance = MagicMock() mock_get_barcode_class.return_value = MagicMock( return_value=mock_barcode_instance ) - buffer = BytesIO() image = Image.new("RGB", (100, 50), color="white") image.save(buffer, format="PNG") @@ -81,12 +81,11 @@ def test_save_barcode( ) barcode_proxy.generate_barcode(data=sample_text) - barcode_proxy.save_barcode() - - barcode_image_path = tmp_path / "test_barcode.png" - barcode_proxy.barcode_image.save(barcode_image_path) - assert barcode_image_path.exists() + # No file system write; we're only checking the in-memory image + barcode_image = barcode_proxy.barcode_image + assert barcode_image is not None + mock_get_barcode_class.assert_called_once() @patch("sage_qrcode.service.barcode.get_barcode_class") def test_generate_barcode_custom_colors( @@ -113,11 +112,9 @@ def test_generate_barcode_custom_colors( assert barcode_proxy.barcode_image is not None mock_get_barcode_class.assert_called_once() - # Additional checks to verify that colors are correctly applied can be done by inspecting the image object - @patch("sage_qrcode.service.barcode.get_barcode_class") - def test_save_barcode_different_formats( - self, mock_get_barcode_class, barcode_proxy, sample_text, tmp_path + def test_save_barcode_different_formats_no_disk_write( + self, mock_get_barcode_class, barcode_proxy, sample_text ): mock_barcode_instance = MagicMock() mock_get_barcode_class.return_value = MagicMock( @@ -133,14 +130,16 @@ def test_save_barcode_different_formats( ) barcode_proxy.generate_barcode(data=sample_text) - barcode_proxy.save_barcode() - # Test saving in PNG format - barcode_image_path_png = tmp_path / "test_barcode.png" - barcode_proxy.barcode_image.save(barcode_image_path_png, format="PNG") - assert barcode_image_path_png.exists() + barcode_image = barcode_proxy.barcode_image + assert barcode_image is not None + + # Test saving in PNG format to buffer + buffer_png = BytesIO() + barcode_image.save(buffer_png, format="PNG") + assert buffer_png.getvalue() != b"" - # Test saving in JPEG format - barcode_image_path_jpeg = tmp_path / "test_barcode.jpeg" - barcode_proxy.barcode_image.save(barcode_image_path_jpeg, format="JPEG") - assert barcode_image_path_jpeg.exists() + # Test saving in JPEG format to buffer + buffer_jpeg = BytesIO() + barcode_image.save(buffer_jpeg, format="JPEG") + assert buffer_jpeg.getvalue() != b""