diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/config/asgi.py b/config/asgi.py index 856079b..a00b3b5 100644 --- a/config/asgi.py +++ b/config/asgi.py @@ -2,6 +2,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') application = get_asgi_application() diff --git a/config/celery.py b/config/celery.py index 08783a9..d03de5c 100644 --- a/config/celery.py +++ b/config/celery.py @@ -3,15 +3,15 @@ import os from celery import Celery # set the default Django settings module for the 'celery' program. -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') -app = Celery("config") +app = Celery('config') # Using a string here means the worker doesn't have to serialize # the configuration object to child processes. # - namespace='CELERY' means all celery-related configuration keys # should have a `CELERY_` prefix. -app.config_from_object("django.conf:settings", namespace="CELERY") +app.config_from_object('django.conf:settings', namespace='CELERY') # Load task modules from all registered Django app configs. app.autodiscover_tasks() diff --git a/config/mixins.py b/config/mixins.py index 491425b..110dc43 100644 --- a/config/mixins.py +++ b/config/mixins.py @@ -8,10 +8,10 @@ from django.views.generic import TemplateView class HTMXFormComponent(TemplateView): form_class: Type[forms.ModelForm] = None - template_name: str = "components/default/form.html" - hx_trigger = "" - view_name = "" - save_button_text = "" + template_name: str = 'components/default/form.html' + hx_trigger = '' + view_name = '' + save_button_text = '' def get_view_name(self): return self.view_name @@ -23,9 +23,7 @@ class HTMXFormComponent(TemplateView): instance = self.get_object() if initial: - return self.form_class( - initial=self.get_initial_values(instance), instance=instance - ) + return self.form_class(initial=self.get_initial_values(instance), instance=instance) return self.form_class(self.request.POST, instance=instance) @@ -38,11 +36,7 @@ class HTMXFormComponent(TemplateView): def get_context_data(self, **kwargs): form = self.get_form() - return { - "form": form, - "url": self.get_view_name(), - "save_button_text": self.save_button_text, - } + return {'form': form, 'url': self.get_view_name(), 'save_button_text': self.save_button_text} def post(self, request, *args, **kwargs): form = self.get_form(initial=False) @@ -50,22 +44,8 @@ class HTMXFormComponent(TemplateView): if form.is_valid(): instance = form.save() - response = render( - request, - self.template_name, - { - "form": form, - "instance": instance, - }, - ) - response["HX-Trigger"] = self.hx_trigger + response = render(request, self.template_name, {'form': form, 'instance': instance}) + response['HX-Trigger'] = self.hx_trigger return response - return render( - request, - self.template_name, - { - "form": form, - "save_button_text": self.save_button_text, - }, - ) + return render(request, self.template_name, {'form': form, 'save_button_text': self.save_button_text}) diff --git a/config/models.py b/config/models.py index 3932cb6..30f2aef 100644 --- a/config/models.py +++ b/config/models.py @@ -8,7 +8,7 @@ class SingletonModel(models.Model): abstract = True def save(self, *args, **kwargs): - cache_key = f"{self._meta.model_name}_singleton_key" + cache_key = f'{self._meta.model_name}_singleton_key' self.pk = 1 super(SingletonModel, self).save(*args, **kwargs) @@ -21,7 +21,7 @@ class SingletonModel(models.Model): @classmethod def load(cls): - cache_key = f"{cls._meta.model_name}_singleton_key" + cache_key = f'{cls._meta.model_name}_singleton_key' data = cache.get(cache_key) if not data: instance, created = cls.objects.get_or_create(pk=1) diff --git a/config/settings/base.py b/config/settings/base.py index f18e398..0d763b6 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -4,9 +4,9 @@ from django.utils.text import gettext_lazy as _ from config.settings.environ import * # noqa -APP_NAME = "Shop" -DESCRIPTION = "" -VERSION = "0.1.0" +APP_NAME = 'Shop' +DESCRIPTION = '' +VERSION = '0.1.0' CORS_ORIGIN_ALLOW_ALL = DEBUG @@ -17,118 +17,98 @@ if not DEBUG: DJANGO_APPS = [ - "unfold", # before django.contrib.admin - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", + 'unfold', # before django.contrib.admin + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', ] THIRD_PARTY_APPS = [ - "corsheaders", - "django_extensions", - "django_filters", - "watchman", - "drf_spectacular", - "gonk", - "rest_framework_simplejwt", + 'corsheaders', + 'django_extensions', + 'django_filters', + 'watchman', + 'drf_spectacular', + 'gonk', + 'rest_framework_simplejwt', ] -PROJECT_APPS = [ - "shop", - "users", - "web", -] +PROJECT_APPS = ['shop', 'users', 'web'] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS if DEBUG: - INSTALLED_APPS += ["django_browser_reload"] + INSTALLED_APPS += ['django_browser_reload'] PROJECT_MIDDLEWARE = [] DJANGO_MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "corsheaders.middleware.CorsMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] MIDDLEWARE = DJANGO_MIDDLEWARE + PROJECT_MIDDLEWARE if DEBUG: - INSTALLED_APPS += ["silk"] - MIDDLEWARE += [ - "silk.middleware.SilkyMiddleware", - "django_browser_reload.middleware.BrowserReloadMiddleware", - ] + INSTALLED_APPS += ['silk'] + MIDDLEWARE += ['silk.middleware.SilkyMiddleware', 'django_browser_reload.middleware.BrowserReloadMiddleware'] -ROOT_URLCONF = "config.urls" +ROOT_URLCONF = 'config.urls' TEMPLATES = [ { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "web.context_processors.web_settings", - ], + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'web.context_processors.web_settings', + ] }, - }, + } ] -WSGI_APPLICATION = "config.wsgi.application" +WSGI_APPLICATION = 'config.wsgi.application' # Database # https://docs.djangoproject.com/en/5.1/ref/settings/#databases -DATABASES = { - "default": DATABASE_URL, -} +DATABASES = {'default': DATABASE_URL} # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, + {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, + {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, + {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, + {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, ] # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = "es" +LANGUAGE_CODE = 'es' -LANGUAGES = ( - ("en", _("Inglés")), - ("es", _("Castellano")), -) +LANGUAGES = (('en', _('Inglés')), ('es', _('Castellano'))) -TIME_ZONE = "Europe/Madrid" +TIME_ZONE = 'Europe/Madrid' USE_I18N = True @@ -140,59 +120,51 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ -STATIC_URL = "/static/" -STATIC_ROOT = BASE_DIR / "static" +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'static' -MEDIA_URL = "/media/" -MEDIA_ROOT = BASE_DIR / "media" +MEDIA_URL = '/media/' +MEDIA_ROOT = BASE_DIR / 'media' -LOGIN_REDIRECT_URL = "/login/" +LOGIN_REDIRECT_URL = '/login/' STORAGES = { - "default": { - "BACKEND": "django.core.files.storage.FileSystemStorage", - }, - "staticfiles": { - "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", - }, + 'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'}, + 'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'}, } # Django Rest Framework Configuration REST_FRAMEWORK = { - "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), - "DEFAULT_AUTHENTICATION_CLASSES": ( - "rest_framework_simplejwt.authentication.JWTAuthentication", - "rest_framework.authentication.SessionAuthentication", - "rest_framework.authentication.BasicAuthentication", + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', ), - "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",), - "DEFAULT_FILTER_BACKENDS": [ - "rest_framework.filters.OrderingFilter", - "django_filters.rest_framework.DjangoFilterBackend", - "rest_framework.filters.SearchFilter", + 'DEFAULT_RENDERER_CLASSES': ('rest_framework.renderers.JSONRenderer',), + 'DEFAULT_FILTER_BACKENDS': [ + 'rest_framework.filters.OrderingFilter', + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', ], - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", - "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", - "PAGE_SIZE": PAGE_SIZE, + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + 'PAGE_SIZE': PAGE_SIZE, } -LOCALE_PATHS = [ - BASE_DIR / "locale", -] +LOCALE_PATHS = [BASE_DIR / 'locale'] if S3_ENABLED: - STORAGES["default"] = { - "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", - } + STORAGES['default'] = {'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage'} -WATCHMAN_STORAGE_PATH = "tmp" +WATCHMAN_STORAGE_PATH = 'tmp' SPECTACULAR_SETTINGS = { - "TITLE": APP_NAME, - "DESCRIPTION": DESCRIPTION, - "VERSION": VERSION, - "SERVE_INCLUDE_SCHEMA": False, + 'TITLE': APP_NAME, + 'DESCRIPTION': DESCRIPTION, + 'VERSION': VERSION, + 'SERVE_INCLUDE_SCHEMA': False, } @@ -200,42 +172,42 @@ EMAIL_USE_TLS = True EMAIL_USE_SSL = False -CELERY_BROKER_URL = env.str("CELERY_BROKER_URL", default="memory://localhost:8000//") +CELERY_BROKER_URL = env.str('CELERY_BROKER_URL', default='memory://localhost:8000//') CELERY_TIME_ZONE = TIME_ZONE CELERY_ALWAYS_EAGER = DEBUG SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": datetime.timedelta(hours=12), - "REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=1), - "ROTATE_REFRESH_TOKENS": False, - "BLACKLIST_AFTER_ROTATION": False, - "UPDATE_LAST_LOGIN": False, - "ALGORITHM": "HS256", - "SIGNING_KEY": SECRET_KEY, - "VERIFYING_KEY": "", - "AUDIENCE": None, - "ISSUER": None, - "JSON_ENCODER": None, - "JWK_URL": None, - "LEEWAY": 0, - "AUTH_HEADER_TYPES": ("Bearer",), - "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", - "USER_ID_FIELD": "id", - "USER_ID_CLAIM": "user_id", - "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", - "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), - "TOKEN_TYPE_CLAIM": "token_type", - "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", - "JTI_CLAIM": "jti", - "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", - "SLIDING_TOKEN_LIFETIME": datetime.timedelta(minutes=5), - "SLIDING_TOKEN_REFRESH_LIFETIME": datetime.timedelta(days=1), - "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer", - "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer", - "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer", - "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer", - "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer", - "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", + 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(hours=12), + 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': False, + 'UPDATE_LAST_LOGIN': False, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': '', + 'AUDIENCE': None, + 'ISSUER': None, + 'JSON_ENCODER': None, + 'JWK_URL': None, + 'LEEWAY': 0, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule', + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', + 'JTI_CLAIM': 'jti', + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': datetime.timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': datetime.timedelta(days=1), + 'TOKEN_OBTAIN_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenObtainPairSerializer', + 'TOKEN_REFRESH_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenRefreshSerializer', + 'TOKEN_VERIFY_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenVerifySerializer', + 'TOKEN_BLACKLIST_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenBlacklistSerializer', + 'SLIDING_TOKEN_OBTAIN_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer', + 'SLIDING_TOKEN_REFRESH_SERIALIZER': 'rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer', } -TAILWIND_APP_NAME = "theme" +TAILWIND_APP_NAME = 'theme' diff --git a/config/settings/environ.py b/config/settings/environ.py index 625e6c9..6f57238 100644 --- a/config/settings/environ.py +++ b/config/settings/environ.py @@ -4,64 +4,56 @@ from shutil import which import environ -BASE_DIR = Path(".") +BASE_DIR = Path('.') env = environ.Env() -env_file = BASE_DIR / ".env" +env_file = BASE_DIR / '.env' if os.path.exists(env_file): environ.Env.read_env(env_file) -SECRET_KEY = env.str("SECRET_KEY", "this-is-the-default-secret-key") -DEBUG = env.bool("DEBUG", False) +SECRET_KEY = env.str('SECRET_KEY', 'this-is-the-default-secret-key') +DEBUG = env.bool('DEBUG', False) -CORS_ORIGIN_WHITELIST = env.str("CORS_ORIGIN_WHITELIST", "http://localhost:8000").split( - "," -) -CSRF_TRUSTED_ORIGINS = env.str("CSRF_TRUSTED_ORIGINS", "http://localhost:8000").split( - "," -) +CORS_ORIGIN_WHITELIST = env.str('CORS_ORIGIN_WHITELIST', 'http://localhost:8000').split(',') +CSRF_TRUSTED_ORIGINS = env.str('CSRF_TRUSTED_ORIGINS', 'http://localhost:8000').split(',') -DATABASE_URL = env.db_url("DATABASE_URL", "sqlite:///db.sqlite3") +DATABASE_URL = env.db_url('DATABASE_URL', 'sqlite:///db.sqlite3') -STATIC_ROOT = env.str("STATIC_ROOT", "static") -MEDIA_ROOT = env.str("MEDIA_ROOT", "media") +STATIC_ROOT = env.str('STATIC_ROOT', 'static') +MEDIA_ROOT = env.str('MEDIA_ROOT', 'media') -REDIS_HOST = env.str("REDIS_HOST", "redis") -REDIS_PORT = env.str("REDIS_PORT", 6379) +REDIS_HOST = env.str('REDIS_HOST', 'redis') +REDIS_PORT = env.str('REDIS_PORT', 6379) -PAGE_SIZE = env.int("PAGE_SIZE", 20) -ALLOWED_HOSTS = env.str("ALLOWED_HOSTS", "*").split(",") +PAGE_SIZE = env.int('PAGE_SIZE', 20) +ALLOWED_HOSTS = env.str('ALLOWED_HOSTS', '*').split(',') -CELERY_BROKER_URL = env.str("CELERY_BROKER_URL", "redis://172.17.0.1:6379/0") +CELERY_BROKER_URL = env.str('CELERY_BROKER_URL', 'redis://172.17.0.1:6379/0') -S3_ENABLED = env.bool("S3_ENABLED", False) +S3_ENABLED = env.bool('S3_ENABLED', False) -AWS_S3_HOST = env.str("S3_HOST", "") -AWS_ACCESS_KEY_ID = env.str("S3_ACCESS_KEY_ID", "") -AWS_SECRET_ACCESS_KEY = env.str("S3_SECRET_ACCESS_KEY", "") -AWS_STORAGE_BUCKET_NAME = env.str("S3_STORAGE_BUCKET_NAME", "") -AWS_S3_ENDPOINT_URL = env.str("S3_ENDPOINT_URL", "") +AWS_S3_HOST = env.str('S3_HOST', '') +AWS_ACCESS_KEY_ID = env.str('S3_ACCESS_KEY_ID', '') +AWS_SECRET_ACCESS_KEY = env.str('S3_SECRET_ACCESS_KEY', '') +AWS_STORAGE_BUCKET_NAME = env.str('S3_STORAGE_BUCKET_NAME', '') +AWS_S3_ENDPOINT_URL = env.str('S3_ENDPOINT_URL', '') # Redsys # https://pagosonline.redsys.es/conexion-redireccion.html -REDSYS_DEBUG = env.bool("REDSYS_DEBUG", DEBUG) -REDSYS_SHARED_SECRET = env.str( - "REDSYS_SHARED_SECRET", "sq7HjrUOBfKmC576ILgskD5srU870gJ7" -) -REDSYS_MERCHANT_CODE = env.str("REDSYS_MERCHANT_CODE", "999008881") -REDSYS_TERMINAL = env.str("REDSYS_TERMINAL", "001") -REDSYS_CURRENCY_CODE = env.str("REDSYS_CURRENCY_CODE", "978") -REDSYS_TPV_DOMAIN = env.str("REDSYS_TPV_DOMAIN", "") +REDSYS_DEBUG = env.bool('REDSYS_DEBUG', DEBUG) +REDSYS_SHARED_SECRET = env.str('REDSYS_SHARED_SECRET', 'sq7HjrUOBfKmC576ILgskD5srU870gJ7') +REDSYS_MERCHANT_CODE = env.str('REDSYS_MERCHANT_CODE', '999008881') +REDSYS_TERMINAL = env.str('REDSYS_TERMINAL', '001') +REDSYS_CURRENCY_CODE = env.str('REDSYS_CURRENCY_CODE', '978') +REDSYS_TPV_DOMAIN = env.str('REDSYS_TPV_DOMAIN', '') -ITEMS_PER_PAGE = env.int("ITEMS_PER_PAGE", 20) -SHOP_COUNTRY = env.str("SHOP_COUNTRY", "ESPAÑA") -NPM_BIN_PATH = which("npm") +ITEMS_PER_PAGE = env.int('ITEMS_PER_PAGE', 20) +SHOP_COUNTRY = env.str('SHOP_COUNTRY', 'ESPAÑA') +NPM_BIN_PATH = which('npm') -EMAIL_BACKEND = env.str( - "EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend" -) -EMAIL_HOST = env.str("EMAIL_HOST", "") -EMAIL_HOST_USER = env.str("EMAIL_HOST_USER", "") -EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", "") -EMAIL_PORT = env.int("EMAIL_PORT", 587) +EMAIL_BACKEND = env.str('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend') +EMAIL_HOST = env.str('EMAIL_HOST', '') +EMAIL_HOST_USER = env.str('EMAIL_HOST_USER', '') +EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD', '') +EMAIL_PORT = env.int('EMAIL_PORT', 587) diff --git a/config/urls.py b/config/urls.py index 9f5fb95..9e07a42 100644 --- a/config/urls.py +++ b/config/urls.py @@ -5,18 +5,15 @@ from django.contrib import admin from django.urls import include, path urlpatterns = [ - path("", include("web.urls", namespace="web")), - path("admin/", admin.site.urls), - path("watchman/", include("watchman.urls")), - path("auth/", include("users.urls", namespace="users")), - path("tpv/", include("shop.urls", namespace="shop")), + path('', include('web.urls', namespace='web')), + path('admin/', admin.site.urls), + path('watchman/', include('watchman.urls')), + path('auth/', include('users.urls', namespace='users')), + path('tpv/', include('shop.urls', namespace='shop')), ] if settings.DEBUG: - urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) + urlpatterns.append(path('__debug__/', include(debug_toolbar.urls))) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - urlpatterns += [ - path("silk/", include("silk.urls")), - path("__reload__/", include("django_browser_reload.urls")), - ] + urlpatterns += [path('silk/', include('silk.urls')), path('__reload__/', include('django_browser_reload.urls'))] diff --git a/config/wsgi.py b/config/wsgi.py index 8509335..a9f185c 100644 --- a/config/wsgi.py +++ b/config/wsgi.py @@ -2,6 +2,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') application = get_wsgi_application() diff --git a/manage.py b/manage.py index d28672e..5e1dd6b 100755 --- a/manage.py +++ b/manage.py @@ -1,22 +1,23 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" + 'available on your PYTHONPATH environment variable? Did you ' + 'forget to activate a virtual environment?' ) from exc execute_from_command_line(sys.argv) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..515a6ad --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,97 @@ +[project] +name = "shoppy" +version = "0.1.1" +description = "" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "boto3==1.34.105", + "celery==5.4.0", + "dj-database-url==1.0.0", + "django==5.1.4", + "django-celery-beat==2.7.0", + "django-cors-headers==3.13.0", + "django-cryptography==1.1", + "django-debug-toolbar==4.4.6", + "django-environ==0.10.0", + "django-extensions==3.2.0", + "django-filter==24.3", + "django-silk==5.1.0", + "django-storages==1.13.2", + "django-unfold==0.42.0", + "django-watchman==1.3.0", + "djangorestframework==3.15.2", + "djangorestframework-simplejwt==5.4.0", + "drf-spectacular==0.26.1", + "gonk==0.6.1", + "ipython==8.16.1", + "pillow==11.0.0", + "psycopg2-binary==2.9.10", + "pydes==2.0.1", + "pytailwindcss==0.2.0", + "redis==4.5.4", + "requests==2.32.3", + "uvicorn==0.21.1", + "xmltodict==0.13.0", +] + +[tool.ruff] +line-length = 119 +indent-width = 4 +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + ".venv", +] + +[tool.ruff.format] +quote-style = "single" +skip-magic-trailing-comma = true + +[tool.ruff.lint] +ignore = ["E501", "E741", "F401", "F403", "F405", "F406"] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "main.settings" +addopts = "--ignore=src" +python_files = ["tests.py", "test_*.py", "*_tests.py"] + +[tool.pyright] +venvPath = "." +venv = ".venv" + +[dependency-groups] +dev = [ + "coverage==7.2.3", + "django-browser-reload==1.17.0", + "django-celery-beat==2.7.0", + "django-debug-toolbar==4.4.6", + "django-silk==5.1.0", + "pytest==7.3.0", + "pytest-cov==4.0.0", + "pytest-django==4.8.0", +] diff --git a/shop/admin.py b/shop/admin.py index 7a71523..7ebdb67 100644 --- a/shop/admin.py +++ b/shop/admin.py @@ -24,123 +24,71 @@ from shop.models import ( # Register your models here. @admin.register(Product) class ProductAdmin(ModelAdmin): - search_fields = ("name",) - list_display = ( - "id", - "name", - "stock", - ) + search_fields = ('name',) + list_display = ('id', 'name', 'stock') @admin.register(ProductPrice) class ProductPriceAdmin(ModelAdmin): - search_fields = ("product",) - autocomplete_fields = ("product",) - list_display = ( - "id", - "product", - "price", - "date", - "tax", - ) + search_fields = ('product',) + autocomplete_fields = ('product',) + list_display = ('id', 'product', 'price', 'date', 'tax') @admin.register(Tax) class TaxAdmin(ModelAdmin): - list_display = ( - "code", - "value", - ) + list_display = ('code', 'value') @admin.register(OrderLine) class OrderLineAdmin(ModelAdmin): - autocomplete_fields = ("product",) - list_display = ( - "id", - "product", - "price", - "quantity", - ) + autocomplete_fields = ('product',) + list_display = ('id', 'product', 'price', 'quantity') @admin.register(Order) class OrderAdmin(ModelAdmin): - list_display = ("id",) + list_display = ('id',) @admin.register(Provider) class ProviderAdmin(ModelAdmin): - list_display = ( - "vat_id", - "name", - ) + list_display = ('vat_id', 'name') @admin.register(Tag) class TagAdmin(ModelAdmin): - list_display = ( - "id", - "name", - ) + list_display = ('id', 'name') @admin.register(Brand) class BrandAdmin(ModelAdmin): - list_display = ( - "id", - "name", - ) + list_display = ('id', 'name') @admin.register(ProductBatch) class ProductBatchAdmin(ModelAdmin): - list_display = ( - "id", - "product", - "quantity", - "provider", - ) + list_display = ('id', 'product', 'quantity', 'provider') @admin.register(Cart) -class ProductBatchAdmin(ModelAdmin): - list_display = ( - "uuid", - "user", - "creation_date", - ) +class CartAdmin(ModelAdmin): + list_display = ('uuid', 'user', 'creation_date') @admin.register(CartItem) -class ProductBatchAdmin(ModelAdmin): - list_display = ( - "id", - "cart", - "product", - "quantity", - ) +class CartItemAdmin(ModelAdmin): + list_display = ('id', 'cart', 'product', 'quantity') @admin.register(ShippingMethod) class ShippingMethodAdmin(ModelAdmin): - list_display = ( - "id", - "name", - "description", - ) + list_display = ('id', 'name', 'description') @admin.register(CustomerAddress) class CustomerAddressAdmin(ModelAdmin): - list_display = ( - "id", - "user", - "vat_id", - "address", - "address_state", - "address_zip", - ) + list_display = ('id', 'user', 'vat_id', 'address', 'address_state', 'address_zip') def get_queryset(self, request): qs = super().get_queryset(request) @@ -149,29 +97,14 @@ class CustomerAddressAdmin(ModelAdmin): @admin.register(ProductCategory) class ProductCategoryAdmin(ModelAdmin): - list_display = ( - "id", - "name", - "parent", - "promoted", - "hidden", - "show_in_navbar", - ) + list_display = ('id', 'name', 'parent', 'promoted', 'hidden', 'show_in_navbar') @admin.register(ShopSettings) class ShopSettingsAdmin(ModelAdmin): - list_display = ( - "id", - "merchant_code", - "currency_code", - "terminal", - ) + list_display = ('id', 'merchant_code', 'currency_code', 'terminal') @admin.register(ProductImage) class ProductImageAdmin(ModelAdmin): - list_display = ( - "id", - "original", - ) + list_display = ('id', 'original') diff --git a/shop/apps.py b/shop/apps.py index 72c606d..fbbdf9c 100644 --- a/shop/apps.py +++ b/shop/apps.py @@ -1,6 +1,8 @@ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class ShopConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "shop" + default_auto_field = 'django.db.models.BigAutoField' + name = 'shop' + verbose_name = _('Tienda') diff --git a/shop/filters.py b/shop/filters.py index 4131f6b..063ea4c 100644 --- a/shop/filters.py +++ b/shop/filters.py @@ -6,44 +6,29 @@ from shop.models import Product, ProductPrice class ProductFilter(django_filters.FilterSet): - name = django_filters.CharFilter( - field_name="name", lookup_expr="icontains", label=_("Nombre") - ) - tags = django_filters.BaseInFilter(field_name="tags", label=_("Etiquetas")) - category = django_filters.BaseInFilter( - field_name="categories", label=_("Categorías") - ) - price_lt = django_filters.NumberFilter(method="filter_price_lt") - price_gt = django_filters.NumberFilter(method="filter_price_gt") - o = OrderingFilter( - fields=( - ("name", "name"), - ("id", "id"), - ) - ) + name = django_filters.CharFilter(field_name='name', lookup_expr='icontains', label=_('Nombre')) + tags = django_filters.BaseInFilter(field_name='tags', label=_('Etiquetas')) + category = django_filters.BaseInFilter(field_name='categories', label=_('Categorías')) + price_lt = django_filters.NumberFilter(method='filter_price_lt') + price_gt = django_filters.NumberFilter(method='filter_price_gt') + o = OrderingFilter(fields=(('name', 'name'), ('id', 'id'))) def filter_price_lt(self, queryset, name, value): - ids = queryset.values("id") - product_ids = ProductPrice.objects.filter( - product_id__in=ids, current=True, price_with_tax__lte=value - ).values("product_id") + ids = queryset.values('id') + product_ids = ProductPrice.objects.filter(product_id__in=ids, current=True, price_with_tax__lte=value).values( + 'product_id' + ) return queryset.filter(pk__in=product_ids) def filter_price_gt(self, queryset, name, value): - ids = queryset.values("id") - product_ids = ProductPrice.objects.filter( - product_id__in=ids, current=True, price_with_tax__gte=value - ).values("product_id") + ids = queryset.values('id') + product_ids = ProductPrice.objects.filter(product_id__in=ids, current=True, price_with_tax__gte=value).values( + 'product_id' + ) return queryset.filter(pk__in=product_ids) class Meta: model = Product - fields = ( - "name", - "stock", - "is_digital_asset", - "tags", - "category", - ) + fields = ('name', 'stock', 'is_digital_asset', 'tags', 'category') diff --git a/shop/migrations/0001_initial.py b/shop/migrations/0001_initial.py index be28736..466cea6 100644 --- a/shop/migrations/0001_initial.py +++ b/shop/migrations/0001_initial.py @@ -10,1406 +10,775 @@ from django.db import migrations, models class Migration(migrations.Migration): - initial = True - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( - name="Brand", + name='Brand', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "name", - models.CharField( - max_length=100, unique=True, verbose_name="nombre" - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('name', models.CharField(max_length=100, unique=True, verbose_name='nombre')), ], - options={ - "verbose_name": "marca", - "verbose_name_plural": "marcas", - }, + options={'verbose_name': 'marca', 'verbose_name_plural': 'marcas'}, ), migrations.CreateModel( - name="Provider", + name='Provider', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('vat_id', models.CharField(max_length=32, unique=True, verbose_name='documento de identidad')), + ('name', models.CharField(max_length=64, verbose_name='nombre')), + ('email', models.EmailField(max_length=254, verbose_name='e-mail de contacto')), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "vat_id", + 'phone', models.CharField( - max_length=32, - unique=True, - verbose_name="documento de identidad", + blank=True, default='', max_length=16, null=True, verbose_name='Teléfono de contacto' ), ), - ("name", models.CharField(max_length=64, verbose_name="nombre")), - ( - "email", - models.EmailField( - max_length=254, verbose_name="e-mail de contacto" - ), - ), - ( - "phone", - models.CharField( - blank=True, - default="", - max_length=16, - null=True, - verbose_name="Teléfono de contacto", - ), - ), - ("address", models.CharField(max_length=255, verbose_name="dirección")), - ("city", models.CharField(max_length=64, verbose_name="ciudad")), - ("state", models.CharField(max_length=64, verbose_name="región")), - ("country", models.CharField(max_length=64, verbose_name="país")), - ("zip", models.CharField(max_length=32, verbose_name="código postal")), + ('address', models.CharField(max_length=255, verbose_name='dirección')), + ('city', models.CharField(max_length=64, verbose_name='ciudad')), + ('state', models.CharField(max_length=64, verbose_name='región')), + ('country', models.CharField(max_length=64, verbose_name='país')), + ('zip', models.CharField(max_length=32, verbose_name='código postal')), ], - options={ - "verbose_name": "proveedor", - "verbose_name_plural": "proveedores", - }, + options={'verbose_name': 'proveedor', 'verbose_name_plural': 'proveedores'}, ), migrations.CreateModel( - name="ShopSettings", + name='ShopSettings', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('merchant_code', models.CharField(max_length=9, verbose_name='Identificación de comercio')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "merchant_code", - models.CharField( - max_length=9, verbose_name="Identificación de comercio" - ), - ), - ( - "currency_code", + 'currency_code', models.CharField( choices=[ - ("8", "LEK ALL"), - ("12", "ALGERIAN DINAR DZD"), - ("24", "ANGOLA KWANZA AOK"), - ("30", "PROBANDO DESA MON"), - ("31", "AZERBAIJANIAN MANAT AZM"), - ("32", "ARGENTINE AUSTRAL ARP"), - ("36", "AUSTRALIAN DOLLAR AUD"), - ("44", "BAHAMIAN DOLLAR BSD"), - ("48", "BAHRAINI DINAR BHD"), - ("50", "TAKA BDT"), - ("51", "ARMENIAN DRAM AMD"), - ("52", "BARBADOS DOLLAR BBD"), - ("60", "BERMUDAN DOLLAR BMD"), - ("64", "NGULTRUM BTN"), - ("68", "BOLIVIAN PESO BOP"), - ("70", "DINAR BAD"), - ("72", "PULA BWP"), - ("76", "CRUZEIRO BRC"), - ("84", "BELIZE DOLLAR 084"), - ("90", "SOLOMON ISLANDS DOLL SBD"), - ("96", "BRUNEI DOLLAR BND"), - ("100", "LEV BGL"), - ("104", "KYAT BUK"), - ("108", "BURUNDI FRANC BIF"), - ("112", "BELARUSSIAN RUBLE BYB"), - ("116", "RIEL KHR"), - ("124", "CANADIAN DOLLAR CAD"), - ("132", "CAPE VERDE ESCUDO CVE"), - ("136", "CAYMAN ISLANDS DOLLA KYD"), - ("144", "SRI LANKA RUPEE LKR"), - ("152", "CHILEAN PESO CLP"), - ("156", "YUAN RENMINBI CNY"), - ("157", "CHINESE RENMIMBI CNH"), - ("158", "CHINESE RENMINBI CNX"), - ("170", "COLOMBIAN PESO COP"), - ("174", "COMOROS FRANC KMF"), - ("180", "ZAIRE ZRZ"), - ("188", "COSTA RICA COLON CRC"), - ("191", "CROATIAN KUNA HRK"), - ("192", "CUBAN PESO CUP"), - ("196", "CYPRUS POUND CYP"), - ("200", "KORUNA CSK"), - ("203", "CZECH KORUNA CZK"), - ("208", "DANISH KRONE DKK"), - ("214", "DOMINICAN PESO DOP"), - ("218", "SUCRE ECS"), - ("222", "EL SALVADOR COLON SVC"), - ("226", "EKWELE GQE"), - ("230", "ETHIOPIAN BIRR ETB"), - ("232", "ERITREAN NAKTAN ERN"), - ("233", "ESTONIAN KROON EEK"), - ("238", "FALKLAND ISLANDS FKP"), - ("242", "FIJI DOLLAR FJD"), - ("262", "DJIBOUTI FRANC DJF"), - ("268", "GEORGIAN LARI GEL"), - ("270", "DALASI GMD"), - ("278", "MARK DER DDR DDM"), - ("288", "GHANA CEDI GHC"), - ("292", "GIBRALTAR POUND GIP"), - ("320", "QUETZAL GTQ"), - ("324", "SYLI GNS"), - ("328", "GUYANA DOLLAR GYD"), - ("332", "GOURDE HTG"), - ("340", "LEMPIRA HNL"), - ("344", "HONG KONG DOLLAR HKD"), - ("348", "FORINT HUF"), - ("352", "ICELAND KRONA ISK"), - ("356", "INDIAN RUPEE INR"), - ("360", "RUPIAH IDR"), - ("364", "IRANIAL RIAL IRR"), - ("365", "IRANIAN AIRLINE RATE IRA"), - ("368", "IRAQI DINAR IQD"), - ("376", "ISRAEL SHEKEL ILS"), - ("388", "JAMAICAN DOLLAR JMD"), - ("392", "YEN JPY"), - ("398", "TENGE KZT"), - ("400", "JORDANIAN DINAR JOD"), - ("404", "KENYAN SHILLING KES"), - ("408", "NORTH KOREAN WON KPW"), - ("410", "KOREAN WON KRW"), - ("414", "KUWAITI DINAR KWD"), - ("417", "KYRGYZSTAN SON KGS"), - ("418", "KIP LAK"), - ("422", "LEBANESE POUND LBP"), - ("426", "LESOTHO LOTI LSM"), - ("428", "LATVIAN LAT LVL"), - ("430", "LIBERIAN DOLLAR LRD"), - ("434", "LIBYAN DINAR LYD"), - ("440", "LITHUANIAN LITAS LTL"), - ("446", "PATACA MOP"), - ("450", "MALAGASY FRANC MGF"), - ("454", "MALAWI KWACHA MWK"), - ("458", "MALASYAN RINGGIT MYR"), - ("462", "MALDIVE RUPEE MVR"), - ("466", "MALI MLF"), - ("470", "MALTESE LIRA MTL"), - ("478", "OUGUIYA MRO"), - ("480", "MAURITIUS RUPEE MUR"), - ("484", "MEXICAN PESO MXP"), - ("496", "TUGRIK MNT"), - ("498", "MOLDOVIAN LEU MDL"), - ("504", "MORROCAN DIRHAM MAD"), - ("508", "METICAL MZM"), - ("512", "RIAL OMANI OMR"), - ("516", "NAMIBIAN DOLLAR NAD"), - ("524", "NEPALESE RUPEE NPR"), - ("532", "NETHERLANDS ANTILLIA ANG"), - ("533", "ARUBA AWG"), - ("536", "YUGOSLAVIAN NEW DIAN NTZ"), - ("548", "VANUATU VATU VUV"), - ("554", "NEW ZEALAND DOLLAR NZD"), - ("556", "NAIRA 566"), - ("558", "CORDOBA NIC"), - ("566", "NAIRA NGN"), - ("578", "NORWEGIAN KRONE NOK"), - ("582", "PACIFIC ISLAND PCI"), - ("586", "PAKISTAN RUPEE PKR"), - ("590", "BALBOA PAB"), - ("598", "KINA PGK"), - ("600", "GUARANI PYG"), - ("604", "PERU INTI PEI"), - ("608", "PHILIPPINE PESO PHP"), - ("616", "ZLOTY PLZ"), - ("624", "GUINEA"), - ("626", "TIMOR ESCUDO TPE"), - ("634", "QATARI RIAL QAR"), - ("642", "LEU ROL"), - ("643", "RUSSIAN ROUBLE RUB"), - ("646", "RWANDA FRANC RWF"), - ("654", "ST.HELENA POUND SHP"), - ("678", "DOBRA STD"), - ("682", "SAUDI RIYAL SAR"), - ("690", "SEYCHELLES RUPEE SCR"), - ("694", "LEONE SLL"), - ("702", "SINGAPORE DOLLAR SGD"), - ("703", "SLOVAK KORUNA SKK"), - ("704", "DONG VND"), - ("705", "SLOVENIAN TOLAR SIT"), - ("706", "SOMALI SHILLING SOS"), - ("710", "RAND ZAR"), - ("716", "ZIMBABWE DOLLAR ZWD"), - ("720", "YEMENI DINAR YDD"), - ("728", "SOUTH SUDANESE POUND SSP"), - ("736", "SUDANESE POUND SDP"), - ("737", "SUDAN AIRLINES SDA"), - ("740", "SURINAM GUILDER SRG"), - ("748", "LILANGENI SZL"), - ("752", "SWEDISH KRONA SEK"), - ("756", "SWISS FRANC CHF"), - ("760", "SYRIAN POUND SYP"), - ("762", "TAJIK RUBLE TJR"), - ("764", "BAHT THB"), - ("776", "PA'ANGA TOP"), - ("780", "TRINIDAD Y TOBAGO DO TTD"), - ("784", "UAE DIRHAM AED"), - ("788", "TUNISIAN DINAR TND"), - ("792", "TURKISH LIRA TRL"), - ("793", "PSEUDO TURKISH LIRA PTL"), - ("795", "MANAT TMM"), - ("800", "UGANDA SHILLING UGS"), - ("804", "KARBOVANET UAK"), - ("807", "MACEDONIAN DENAR MKD"), - ("810", "RUSSIAN ROUBLE RUR"), - ("818", "EGYPTIAN POUND EGP"), - ("826", "POUND STERLING GBP"), - ("834", "TANZANIAN SHILLING TZS"), - ("840", "DOLAR U.S.A. USD"), - ("858", "URUGUAYAN PESO UYP"), - ("860", "UZBEKISTAN SUM UZS"), - ("862", "BOLIVAR VEB"), - ("882", "TALA WST"), - ("886", "YEMINI RIAL YER"), - ("890", "NEW YUGOSLAVIAN DOLL YUD"), - ("891", "NEW DINAR YUG"), - ("894", "KWACHA ZMK"), - ("901", "NEW TAIWAN DOLLAR TWD"), - ("934", "NEW MANAT TMT"), - ("936", "GHANA CEDI GHS"), - ("941", "DINAR SERBIO RSD"), - ("943", "MOZAMBIQUE METICAL MZN"), - ("944", "AZERBAIJANIAN MANAT AZN"), - ("946", "NEW LEU RON"), - ("949", "TURKISH LIRA TRY"), - ("950", "CFA FRANC BEAC XAF"), - ("951", "EAST CARIBBEAN DOLLA XCD"), - ("952", "CFA FRANC BCEAO XOF"), - ("953", "CFP FRANC XPF"), - ("954", "E.C.U. EUROPEAN CUR XEU"), - ("967", "KWACHA ZMW"), - ("968", "SURINAME DOLLAR SRD"), - ("969", "ARIARY MGA"), - ("971", "AFGHANISTAN AFGHANI AFN"), - ("972", "TAJIKISTAN SOMONI TJS"), - ("973", "KWANZA ANGOLA AOA"), - ("974", "BELARUSSIAN RUBLE BYR"), - ("975", "NEW LEV BGN"), - ("976", "FRANCO DEL CONGO CDF"), - ("977", "BOSNIAN MARKA BAM"), - ("978", "EURO EUR"), - ("980", "HRYVNIA UAH"), - ("981", "GEORGIAN LARI GEL"), - ("985", "NEW POLISH ZLOTY PLN"), - ("986", "BRAZILIAN REAL BRL"), - ("991", "RAND FINANCIER ZAL"), + ('8', 'LEK ALL'), + ('12', 'ALGERIAN DINAR DZD'), + ('24', 'ANGOLA KWANZA AOK'), + ('30', 'PROBANDO DESA MON'), + ('31', 'AZERBAIJANIAN MANAT AZM'), + ('32', 'ARGENTINE AUSTRAL ARP'), + ('36', 'AUSTRALIAN DOLLAR AUD'), + ('44', 'BAHAMIAN DOLLAR BSD'), + ('48', 'BAHRAINI DINAR BHD'), + ('50', 'TAKA BDT'), + ('51', 'ARMENIAN DRAM AMD'), + ('52', 'BARBADOS DOLLAR BBD'), + ('60', 'BERMUDAN DOLLAR BMD'), + ('64', 'NGULTRUM BTN'), + ('68', 'BOLIVIAN PESO BOP'), + ('70', 'DINAR BAD'), + ('72', 'PULA BWP'), + ('76', 'CRUZEIRO BRC'), + ('84', 'BELIZE DOLLAR 084'), + ('90', 'SOLOMON ISLANDS DOLL SBD'), + ('96', 'BRUNEI DOLLAR BND'), + ('100', 'LEV BGL'), + ('104', 'KYAT BUK'), + ('108', 'BURUNDI FRANC BIF'), + ('112', 'BELARUSSIAN RUBLE BYB'), + ('116', 'RIEL KHR'), + ('124', 'CANADIAN DOLLAR CAD'), + ('132', 'CAPE VERDE ESCUDO CVE'), + ('136', 'CAYMAN ISLANDS DOLLA KYD'), + ('144', 'SRI LANKA RUPEE LKR'), + ('152', 'CHILEAN PESO CLP'), + ('156', 'YUAN RENMINBI CNY'), + ('157', 'CHINESE RENMIMBI CNH'), + ('158', 'CHINESE RENMINBI CNX'), + ('170', 'COLOMBIAN PESO COP'), + ('174', 'COMOROS FRANC KMF'), + ('180', 'ZAIRE ZRZ'), + ('188', 'COSTA RICA COLON CRC'), + ('191', 'CROATIAN KUNA HRK'), + ('192', 'CUBAN PESO CUP'), + ('196', 'CYPRUS POUND CYP'), + ('200', 'KORUNA CSK'), + ('203', 'CZECH KORUNA CZK'), + ('208', 'DANISH KRONE DKK'), + ('214', 'DOMINICAN PESO DOP'), + ('218', 'SUCRE ECS'), + ('222', 'EL SALVADOR COLON SVC'), + ('226', 'EKWELE GQE'), + ('230', 'ETHIOPIAN BIRR ETB'), + ('232', 'ERITREAN NAKTAN ERN'), + ('233', 'ESTONIAN KROON EEK'), + ('238', 'FALKLAND ISLANDS FKP'), + ('242', 'FIJI DOLLAR FJD'), + ('262', 'DJIBOUTI FRANC DJF'), + ('268', 'GEORGIAN LARI GEL'), + ('270', 'DALASI GMD'), + ('278', 'MARK DER DDR DDM'), + ('288', 'GHANA CEDI GHC'), + ('292', 'GIBRALTAR POUND GIP'), + ('320', 'QUETZAL GTQ'), + ('324', 'SYLI GNS'), + ('328', 'GUYANA DOLLAR GYD'), + ('332', 'GOURDE HTG'), + ('340', 'LEMPIRA HNL'), + ('344', 'HONG KONG DOLLAR HKD'), + ('348', 'FORINT HUF'), + ('352', 'ICELAND KRONA ISK'), + ('356', 'INDIAN RUPEE INR'), + ('360', 'RUPIAH IDR'), + ('364', 'IRANIAL RIAL IRR'), + ('365', 'IRANIAN AIRLINE RATE IRA'), + ('368', 'IRAQI DINAR IQD'), + ('376', 'ISRAEL SHEKEL ILS'), + ('388', 'JAMAICAN DOLLAR JMD'), + ('392', 'YEN JPY'), + ('398', 'TENGE KZT'), + ('400', 'JORDANIAN DINAR JOD'), + ('404', 'KENYAN SHILLING KES'), + ('408', 'NORTH KOREAN WON KPW'), + ('410', 'KOREAN WON KRW'), + ('414', 'KUWAITI DINAR KWD'), + ('417', 'KYRGYZSTAN SON KGS'), + ('418', 'KIP LAK'), + ('422', 'LEBANESE POUND LBP'), + ('426', 'LESOTHO LOTI LSM'), + ('428', 'LATVIAN LAT LVL'), + ('430', 'LIBERIAN DOLLAR LRD'), + ('434', 'LIBYAN DINAR LYD'), + ('440', 'LITHUANIAN LITAS LTL'), + ('446', 'PATACA MOP'), + ('450', 'MALAGASY FRANC MGF'), + ('454', 'MALAWI KWACHA MWK'), + ('458', 'MALASYAN RINGGIT MYR'), + ('462', 'MALDIVE RUPEE MVR'), + ('466', 'MALI MLF'), + ('470', 'MALTESE LIRA MTL'), + ('478', 'OUGUIYA MRO'), + ('480', 'MAURITIUS RUPEE MUR'), + ('484', 'MEXICAN PESO MXP'), + ('496', 'TUGRIK MNT'), + ('498', 'MOLDOVIAN LEU MDL'), + ('504', 'MORROCAN DIRHAM MAD'), + ('508', 'METICAL MZM'), + ('512', 'RIAL OMANI OMR'), + ('516', 'NAMIBIAN DOLLAR NAD'), + ('524', 'NEPALESE RUPEE NPR'), + ('532', 'NETHERLANDS ANTILLIA ANG'), + ('533', 'ARUBA AWG'), + ('536', 'YUGOSLAVIAN NEW DIAN NTZ'), + ('548', 'VANUATU VATU VUV'), + ('554', 'NEW ZEALAND DOLLAR NZD'), + ('556', 'NAIRA 566'), + ('558', 'CORDOBA NIC'), + ('566', 'NAIRA NGN'), + ('578', 'NORWEGIAN KRONE NOK'), + ('582', 'PACIFIC ISLAND PCI'), + ('586', 'PAKISTAN RUPEE PKR'), + ('590', 'BALBOA PAB'), + ('598', 'KINA PGK'), + ('600', 'GUARANI PYG'), + ('604', 'PERU INTI PEI'), + ('608', 'PHILIPPINE PESO PHP'), + ('616', 'ZLOTY PLZ'), + ('624', 'GUINEA'), + ('626', 'TIMOR ESCUDO TPE'), + ('634', 'QATARI RIAL QAR'), + ('642', 'LEU ROL'), + ('643', 'RUSSIAN ROUBLE RUB'), + ('646', 'RWANDA FRANC RWF'), + ('654', 'ST.HELENA POUND SHP'), + ('678', 'DOBRA STD'), + ('682', 'SAUDI RIYAL SAR'), + ('690', 'SEYCHELLES RUPEE SCR'), + ('694', 'LEONE SLL'), + ('702', 'SINGAPORE DOLLAR SGD'), + ('703', 'SLOVAK KORUNA SKK'), + ('704', 'DONG VND'), + ('705', 'SLOVENIAN TOLAR SIT'), + ('706', 'SOMALI SHILLING SOS'), + ('710', 'RAND ZAR'), + ('716', 'ZIMBABWE DOLLAR ZWD'), + ('720', 'YEMENI DINAR YDD'), + ('728', 'SOUTH SUDANESE POUND SSP'), + ('736', 'SUDANESE POUND SDP'), + ('737', 'SUDAN AIRLINES SDA'), + ('740', 'SURINAM GUILDER SRG'), + ('748', 'LILANGENI SZL'), + ('752', 'SWEDISH KRONA SEK'), + ('756', 'SWISS FRANC CHF'), + ('760', 'SYRIAN POUND SYP'), + ('762', 'TAJIK RUBLE TJR'), + ('764', 'BAHT THB'), + ('776', "PA'ANGA TOP"), + ('780', 'TRINIDAD Y TOBAGO DO TTD'), + ('784', 'UAE DIRHAM AED'), + ('788', 'TUNISIAN DINAR TND'), + ('792', 'TURKISH LIRA TRL'), + ('793', 'PSEUDO TURKISH LIRA PTL'), + ('795', 'MANAT TMM'), + ('800', 'UGANDA SHILLING UGS'), + ('804', 'KARBOVANET UAK'), + ('807', 'MACEDONIAN DENAR MKD'), + ('810', 'RUSSIAN ROUBLE RUR'), + ('818', 'EGYPTIAN POUND EGP'), + ('826', 'POUND STERLING GBP'), + ('834', 'TANZANIAN SHILLING TZS'), + ('840', 'DOLAR U.S.A. USD'), + ('858', 'URUGUAYAN PESO UYP'), + ('860', 'UZBEKISTAN SUM UZS'), + ('862', 'BOLIVAR VEB'), + ('882', 'TALA WST'), + ('886', 'YEMINI RIAL YER'), + ('890', 'NEW YUGOSLAVIAN DOLL YUD'), + ('891', 'NEW DINAR YUG'), + ('894', 'KWACHA ZMK'), + ('901', 'NEW TAIWAN DOLLAR TWD'), + ('934', 'NEW MANAT TMT'), + ('936', 'GHANA CEDI GHS'), + ('941', 'DINAR SERBIO RSD'), + ('943', 'MOZAMBIQUE METICAL MZN'), + ('944', 'AZERBAIJANIAN MANAT AZN'), + ('946', 'NEW LEU RON'), + ('949', 'TURKISH LIRA TRY'), + ('950', 'CFA FRANC BEAC XAF'), + ('951', 'EAST CARIBBEAN DOLLA XCD'), + ('952', 'CFA FRANC BCEAO XOF'), + ('953', 'CFP FRANC XPF'), + ('954', 'E.C.U. EUROPEAN CUR XEU'), + ('967', 'KWACHA ZMW'), + ('968', 'SURINAME DOLLAR SRD'), + ('969', 'ARIARY MGA'), + ('971', 'AFGHANISTAN AFGHANI AFN'), + ('972', 'TAJIKISTAN SOMONI TJS'), + ('973', 'KWANZA ANGOLA AOA'), + ('974', 'BELARUSSIAN RUBLE BYR'), + ('975', 'NEW LEV BGN'), + ('976', 'FRANCO DEL CONGO CDF'), + ('977', 'BOSNIAN MARKA BAM'), + ('978', 'EURO EUR'), + ('980', 'HRYVNIA UAH'), + ('981', 'GEORGIAN LARI GEL'), + ('985', 'NEW POLISH ZLOTY PLN'), + ('986', 'BRAZILIAN REAL BRL'), + ('991', 'RAND FINANCIER ZAL'), ], - default="978", + default='978', max_length=4, - verbose_name="Código de moneda", - ), - ), - ("terminal", models.CharField(max_length=8, verbose_name="Terminal")), - ( - "shared_secret", - models.CharField(max_length=100, verbose_name="Clave de Redsys"), - ), - ( - "tpv_domain", - models.URLField( - blank=True, - max_length=128, - null=True, - verbose_name="dominio del tpv", + verbose_name='Código de moneda', ), ), + ('terminal', models.CharField(max_length=8, verbose_name='Terminal')), + ('shared_secret', models.CharField(max_length=100, verbose_name='Clave de Redsys')), + ('tpv_domain', models.URLField(blank=True, max_length=128, null=True, verbose_name='dominio del tpv')), ], - options={ - "verbose_name": "ajustes de la tienda", - "verbose_name_plural": "ajustes de la tienda", - }, + options={'verbose_name': 'ajustes de la tienda', 'verbose_name_plural': 'ajustes de la tienda'}, ), migrations.CreateModel( - name="Tag", + name='Tag', fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=16)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=16)), ], - options={ - "verbose_name": "etiqueta", - "verbose_name_plural": "etiquetas", - "ordering": ("name",), - }, + options={'verbose_name': 'etiqueta', 'verbose_name_plural': 'etiquetas', 'ordering': ('name',)}, ), migrations.CreateModel( - name="Tax", + name='Tax', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "code", - models.CharField( - max_length=8, unique=True, verbose_name="código de impuesto" - ), - ), - ( - "value", - models.PositiveIntegerField( - verbose_name="valor entero (porcentaje)" - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('code', models.CharField(max_length=8, unique=True, verbose_name='código de impuesto')), + ('value', models.PositiveIntegerField(verbose_name='valor entero (porcentaje)')), ], - options={ - "verbose_name": "impuesto", - "verbose_name_plural": "impuestos", - "ordering": ("id",), - }, + options={'verbose_name': 'impuesto', 'verbose_name_plural': 'impuestos', 'ordering': ('id',)}, ), migrations.CreateModel( - name="Cart", + name='Cart', fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='uuid')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "uuid", - models.UUIDField( - default=uuid.uuid4, - primary_key=True, - serialize=False, - verbose_name="uuid", - ), - ), - ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "user", + 'user', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name="usuario", + verbose_name='usuario', ), ), ], - options={ - "verbose_name": "carrito", - "verbose_name_plural": "carritos", - }, + options={'verbose_name': 'carrito', 'verbose_name_plural': 'carritos'}, ), migrations.CreateModel( - name="CustomerAddress", + name='CustomerAddress', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "vat_id", + 'vat_id', models.CharField( blank=True, - default="", + default='', max_length=16, null=True, - verbose_name="número de identificación fiscal", + verbose_name='número de identificación fiscal', ), ), + ('full_name', models.CharField(max_length=128, verbose_name='dirección')), + ('address', models.CharField(max_length=128, verbose_name='dirección')), + ('address_town', models.CharField(max_length=64, verbose_name='localidad')), + ('address_zip', models.CharField(max_length=16, verbose_name='código postal')), + ('address_state', models.CharField(max_length=64, verbose_name='provincia')), + ('address_country', models.CharField(default='ESPAÑA', max_length=64, verbose_name='país')), ( - "full_name", - models.CharField(max_length=128, verbose_name="dirección"), - ), - ("address", models.CharField(max_length=128, verbose_name="dirección")), - ( - "address_town", - models.CharField(max_length=64, verbose_name="localidad"), + 'address_phone', + models.CharField(blank=True, default='', max_length=16, null=True, verbose_name='teléfono'), ), ( - "address_zip", - models.CharField(max_length=16, verbose_name="código postal"), - ), - ( - "address_state", - models.CharField(max_length=64, verbose_name="provincia"), - ), - ( - "address_country", + 'address_type', models.CharField( - default="ESPAÑA", max_length=64, verbose_name="país" - ), - ), - ( - "address_phone", - models.CharField( - blank=True, - default="", - max_length=16, - null=True, - verbose_name="teléfono", - ), - ), - ( - "address_type", - models.CharField( - choices=[("BILL", "facturación"), ("SHIP", "envío")], - default="SHIP", + choices=[('BILL', 'facturación'), ('SHIP', 'envío')], + default='SHIP', max_length=4, - verbose_name="tipo de dirección", + verbose_name='tipo de dirección', ), ), - ("email", models.EmailField(max_length=254, verbose_name="e-mail")), + ('email', models.EmailField(max_length=254, verbose_name='e-mail')), + ('default', models.BooleanField(default=True, verbose_name='por defecto')), + ('hidden', models.BooleanField(db_index=True, default=False, verbose_name='oculto')), ( - "default", - models.BooleanField(default=True, verbose_name="por defecto"), - ), - ( - "hidden", - models.BooleanField( - db_index=True, default=False, verbose_name="oculto" - ), - ), - ( - "user", + 'user', models.ForeignKey( null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name="usuario", + verbose_name='usuario', ), ), ], - options={ - "verbose_name": "dirección de cliente", - "verbose_name_plural": "direcciones de cliente", - }, + options={'verbose_name': 'dirección de cliente', 'verbose_name_plural': 'direcciones de cliente'}, ), migrations.CreateModel( - name="Order", + name='Order', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, verbose_name='UUID')), + ('code', models.CharField(default=shop.models.create_order_code, max_length=20, unique=True)), + ('status', models.CharField(default='PEN', max_length=3, verbose_name='estado')), + ('shipping_status', models.CharField(default='NOT', max_length=3, verbose_name='estado de envío')), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "uuid", - models.UUIDField( - db_index=True, default=uuid.uuid4, verbose_name="UUID" - ), - ), - ( - "code", - models.CharField( - default=shop.models.create_order_code, - max_length=20, - unique=True, - ), - ), - ( - "status", - models.CharField( - default="PEN", max_length=3, verbose_name="estado" - ), - ), - ( - "shipping_status", - models.CharField( - default="NOT", max_length=3, verbose_name="estado de envío" - ), - ), - ( - "base_total", + 'base_total', models.DecimalField( - decimal_places=2, - default=Decimal("0"), - max_digits=13, - verbose_name="base imponible", + decimal_places=2, default=Decimal('0'), max_digits=13, verbose_name='base imponible' ), ), ( - "total", + 'total', + models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=13, verbose_name='total'), + ), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='e-mail')), + ('billing_address', models.CharField(max_length=255, verbose_name='dirección de facturación')), + ('billing_city', models.CharField(max_length=64, verbose_name='ciudad de facturación')), + ('billing_state', models.CharField(max_length=64, verbose_name='región de facturación')), + ('billing_country', models.CharField(max_length=64, verbose_name='país de facturación')), + ('billing_zip', models.CharField(max_length=32, verbose_name='código postal de facturación')), + ('shipping_address', models.CharField(max_length=255, verbose_name='dirección')), + ('shipping_city', models.CharField(max_length=64, verbose_name='ciudad')), + ('shipping_state', models.CharField(max_length=64, verbose_name='región')), + ('shipping_country', models.CharField(max_length=64, verbose_name='país')), + ('shipping_zip', models.CharField(max_length=32, verbose_name='código postal')), + ('contact_phone', models.CharField(blank=True, max_length=32, verbose_name='teléfono de contacto')), + ( + 'amount_paid', models.DecimalField( - decimal_places=2, - default=Decimal("0"), - max_digits=13, - verbose_name="total", + decimal_places=2, default=Decimal('0.00'), max_digits=11, verbose_name='cantidad pagada' ), ), ( - "email", - models.EmailField( - blank=True, max_length=254, verbose_name="e-mail" - ), - ), - ( - "billing_address", - models.CharField( - max_length=255, verbose_name="dirección de facturación" - ), - ), - ( - "billing_city", - models.CharField( - max_length=64, verbose_name="ciudad de facturación" - ), - ), - ( - "billing_state", - models.CharField( - max_length=64, verbose_name="región de facturación" - ), - ), - ( - "billing_country", - models.CharField(max_length=64, verbose_name="país de facturación"), - ), - ( - "billing_zip", - models.CharField( - max_length=32, verbose_name="código postal de facturación" - ), - ), - ( - "shipping_address", - models.CharField(max_length=255, verbose_name="dirección"), - ), - ( - "shipping_city", - models.CharField(max_length=64, verbose_name="ciudad"), - ), - ( - "shipping_state", - models.CharField(max_length=64, verbose_name="región"), - ), - ( - "shipping_country", - models.CharField(max_length=64, verbose_name="país"), - ), - ( - "shipping_zip", - models.CharField(max_length=32, verbose_name="código postal"), - ), - ( - "contact_phone", - models.CharField( - blank=True, max_length=32, verbose_name="teléfono de contacto" - ), - ), - ( - "amount_paid", - models.DecimalField( - decimal_places=2, - default=Decimal("0.00"), - max_digits=11, - verbose_name="cantidad pagada", - ), - ), - ( - "from_cart", + 'from_cart', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="shop.cart", - verbose_name="carrito de origen de pedido", + to='shop.cart', + verbose_name='carrito de origen de pedido', ), ), ( - "user", + 'user', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, - verbose_name="cliente", + verbose_name='cliente', ), ), ], - options={ - "verbose_name": "pedido", - "verbose_name_plural": "pedidos", - "ordering": ("creation_date",), - }, + options={'verbose_name': 'pedido', 'verbose_name_plural': 'pedidos', 'ordering': ('creation_date',)}, ), migrations.CreateModel( - name="Payment", + name='Payment', fields=[ + ('hash', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='Hash')), ( - "hash", - models.UUIDField( - default=uuid.uuid4, - primary_key=True, - serialize=False, - verbose_name="Hash", - ), + 'amount', + models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=8, verbose_name='Cantidad'), ), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='Fecha de creación')), + ('metadata', models.JSONField(default=dict, verbose_name='Metadata')), ( - "amount", - models.DecimalField( - decimal_places=2, - default=Decimal("0"), - max_digits=8, - verbose_name="Cantidad", - ), - ), - ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="Fecha de creación" - ), - ), - ("metadata", models.JSONField(default=dict, verbose_name="Metadata")), - ( - "method", + 'method', models.CharField( - choices=[("REDSYS", "Redsys")], - default="REDSYS", - max_length=6, - verbose_name="método de pago", + choices=[('REDSYS', 'Redsys')], default='REDSYS', max_length=6, verbose_name='método de pago' ), ), ( - "order", + 'order', models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, - related_name="payments", - to="shop.order", - verbose_name="pedido", + related_name='payments', + to='shop.order', + verbose_name='pedido', ), ), ( - "user", + 'user', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, - verbose_name="cliente", + verbose_name='cliente', ), ), ], - options={ - "verbose_name": "Pago", - "verbose_name_plural": "Pagos", - }, + options={'verbose_name': 'Pago', 'verbose_name_plural': 'Pagos'}, ), migrations.CreateModel( - name="Product", + name='Product', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('sku', models.CharField(max_length=64, unique=True, verbose_name='código de referencia')), + ('name', models.CharField(max_length=96, verbose_name='nombre')), + ('description', models.TextField(blank=True, default='', verbose_name='descripción')), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), + 'stock', + models.DecimalField(decimal_places=4, default=Decimal('0'), max_digits=13, verbose_name='stock'), ), + ('is_digital_asset', models.BooleanField(default=False, verbose_name='es un activo digital')), + ('url', models.URLField(blank=True, verbose_name='URL de descarga')), + ('slug', models.SlugField(blank=True, default='', max_length=128, null=True, verbose_name='Slug')), + ('hidden', models.BooleanField(db_index=True, default=False, verbose_name='oculto')), + ('is_shipping_method', models.BooleanField(default=False, verbose_name='es forma de envío')), ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "sku", - models.CharField( - max_length=64, unique=True, verbose_name="código de referencia" - ), - ), - ("name", models.CharField(max_length=96, verbose_name="nombre")), - ( - "description", - models.TextField( - blank=True, default="", verbose_name="descripción" - ), - ), - ( - "stock", - models.DecimalField( - decimal_places=4, - default=Decimal("0"), - max_digits=13, - verbose_name="stock", - ), - ), - ( - "is_digital_asset", - models.BooleanField( - default=False, verbose_name="es un activo digital" - ), - ), - ("url", models.URLField(blank=True, verbose_name="URL de descarga")), - ( - "slug", - models.SlugField( - blank=True, - default="", - max_length=128, - null=True, - verbose_name="Slug", - ), - ), - ( - "hidden", - models.BooleanField( - db_index=True, default=False, verbose_name="oculto" - ), - ), - ( - "is_shipping_method", - models.BooleanField( - default=False, verbose_name="es forma de envío" - ), - ), - ( - "brand", + 'brand', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="shop.brand", - verbose_name="marca", + to='shop.brand', + verbose_name='marca', ), ), ], - options={ - "verbose_name": "producto", - "verbose_name_plural": "productos", - "ordering": ("id",), - }, + options={'verbose_name': 'producto', 'verbose_name_plural': 'productos', 'ordering': ('id',)}, ), migrations.CreateModel( - name="OrderLine", + name='OrderLine', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "quantity", + 'quantity', models.DecimalField( - decimal_places=4, - default=Decimal("1"), - max_digits=13, - verbose_name="cantidad", + decimal_places=4, default=Decimal('1'), max_digits=13, verbose_name='cantidad' ), ), + ('price', models.DecimalField(decimal_places=4, max_digits=13, verbose_name='precio')), ( - "price", + 'base_total', models.DecimalField( - decimal_places=4, max_digits=13, verbose_name="precio" + decimal_places=4, default=Decimal('0'), max_digits=13, verbose_name='total sin impuestos' ), ), + ('tax_value', models.PositiveIntegerField(verbose_name='valor de impuestos')), + ('taxes', models.DecimalField(decimal_places=4, max_digits=13, verbose_name='impuestos')), + ('total', models.DecimalField(decimal_places=4, max_digits=13, verbose_name='total')), ( - "base_total", - models.DecimalField( - decimal_places=4, - default=Decimal("0"), - max_digits=13, - verbose_name="total sin impuestos", - ), - ), - ( - "tax_value", - models.PositiveIntegerField(verbose_name="valor de impuestos"), - ), - ( - "taxes", - models.DecimalField( - decimal_places=4, max_digits=13, verbose_name="impuestos" - ), - ), - ( - "total", - models.DecimalField( - decimal_places=4, max_digits=13, verbose_name="total" - ), - ), - ( - "order", + 'order', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="lines", - to="shop.order", - verbose_name="pedido", + related_name='lines', + to='shop.order', + verbose_name='pedido', ), ), ( - "product", + 'product', models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="shop.product", - verbose_name="producto", + on_delete=django.db.models.deletion.CASCADE, to='shop.product', verbose_name='producto' ), ), ], - options={ - "verbose_name": "línea de pedido", - "verbose_name_plural": "líneas de pedido", - }, + options={'verbose_name': 'línea de pedido', 'verbose_name_plural': 'líneas de pedido'}, ), migrations.CreateModel( - name="CartItem", + name='CartItem', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1, verbose_name='cantidad')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", + 'cart', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='items', + to='shop.cart', + verbose_name='carrito', ), ), ( - "quantity", - models.PositiveIntegerField(default=1, verbose_name="cantidad"), - ), - ( - "cart", + 'product', models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="items", - to="shop.cart", - verbose_name="carrito", - ), - ), - ( - "product", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="shop.product", - verbose_name="producto", + on_delete=django.db.models.deletion.CASCADE, to='shop.product', verbose_name='producto' ), ), ], - options={ - "verbose_name": "línea de carrito", - "verbose_name_plural": "líneas de carrito", - }, + options={'verbose_name': 'línea de carrito', 'verbose_name_plural': 'líneas de carrito'}, ), migrations.CreateModel( - name="ProductCategory", + name='ProductCategory', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32)), + ('promoted', models.BooleanField(default=False, verbose_name='promocionada')), + ('hidden', models.BooleanField(default=False, verbose_name='oculta')), + ('show_in_navbar', models.BooleanField(default=False, verbose_name='mostrar en cabecera')), + ('slug', models.SlugField(blank=True, default='', max_length=48, unique=True, verbose_name='slug')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=32)), - ( - "promoted", - models.BooleanField(default=False, verbose_name="promocionada"), - ), - ("hidden", models.BooleanField(default=False, verbose_name="oculta")), - ( - "show_in_navbar", - models.BooleanField( - default=False, verbose_name="mostrar en cabecera" - ), - ), - ( - "slug", - models.SlugField( - blank=True, - default="", - max_length=48, - unique=True, - verbose_name="slug", - ), - ), - ( - "parent", + 'parent', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="shop.productcategory", - verbose_name="categoría padre", + to='shop.productcategory', + verbose_name='categoría padre', ), ), ], - options={ - "verbose_name": "categoría", - "verbose_name_plural": "categorías", - "ordering": ("name",), - }, + options={'verbose_name': 'categoría', 'verbose_name_plural': 'categorías', 'ordering': ('name',)}, ), migrations.AddField( - model_name="product", - name="categories", - field=models.ManyToManyField( - blank=True, to="shop.productcategory", verbose_name="categorías" - ), + model_name='product', + name='categories', + field=models.ManyToManyField(blank=True, to='shop.productcategory', verbose_name='categorías'), ), migrations.CreateModel( - name="ProductImage", + name='ProductImage', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('original', models.ImageField(upload_to='uploads', verbose_name='Imagen original')), + ('xl', models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='XL')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'xl_dark', + models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='XL (fondo oscuro)'), + ), + ('l', models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='L')), + ( + 'l_dark', + models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='L (fondo oscuro)'), + ), + ('m', models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='M')), + ( + 'm_dark', + models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='M (fondo oscuro)'), + ), + ('s', models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='S')), + ( + 's_dark', + models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='S (fondo oscuro)'), + ), + ('xs', models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='XS')), + ( + 'xs_dark', + models.ImageField(blank=True, null=True, upload_to='uploads', verbose_name='XS (fondo oscuro)'), ), ( - "original", - models.ImageField( - upload_to="uploads", verbose_name="Imagen original" - ), - ), - ( - "xl", - models.ImageField( - blank=True, null=True, upload_to="uploads", verbose_name="XL" - ), - ), - ( - "xl_dark", - models.ImageField( - blank=True, - null=True, - upload_to="uploads", - verbose_name="XL (fondo oscuro)", - ), - ), - ( - "l", - models.ImageField( - blank=True, null=True, upload_to="uploads", verbose_name="L" - ), - ), - ( - "l_dark", - models.ImageField( - blank=True, - null=True, - upload_to="uploads", - verbose_name="L (fondo oscuro)", - ), - ), - ( - "m", - models.ImageField( - blank=True, null=True, upload_to="uploads", verbose_name="M" - ), - ), - ( - "m_dark", - models.ImageField( - blank=True, - null=True, - upload_to="uploads", - verbose_name="M (fondo oscuro)", - ), - ), - ( - "s", - models.ImageField( - blank=True, null=True, upload_to="uploads", verbose_name="S" - ), - ), - ( - "s_dark", - models.ImageField( - blank=True, - null=True, - upload_to="uploads", - verbose_name="S (fondo oscuro)", - ), - ), - ( - "xs", - models.ImageField( - blank=True, null=True, upload_to="uploads", verbose_name="XS" - ), - ), - ( - "xs_dark", - models.ImageField( - blank=True, - null=True, - upload_to="uploads", - verbose_name="XS (fondo oscuro)", - ), - ), - ( - "product", + 'product', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="images", - to="shop.product", - verbose_name="Producto", + related_name='images', + to='shop.product', + verbose_name='Producto', ), ), ], ), migrations.CreateModel( - name="ProductBatch", + name='ProductBatch', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('code', models.CharField(db_index=True, max_length=32, unique=True, verbose_name='código')), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "code", - models.CharField( - db_index=True, max_length=32, unique=True, verbose_name="código" - ), - ), - ( - "quantity", + 'quantity', models.DecimalField( - decimal_places=4, - default=Decimal("1"), - max_digits=13, - verbose_name="cantidad", + decimal_places=4, default=Decimal('1'), max_digits=13, verbose_name='cantidad' ), ), ( - "expiration_date", - models.DateField( - blank=True, - null=True, - verbose_name="fecha de caducidad / consumo preferente", - ), + 'expiration_date', + models.DateField(blank=True, null=True, verbose_name='fecha de caducidad / consumo preferente'), ), ( - "product", + 'product', models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="shop.product", - verbose_name="producto", + on_delete=django.db.models.deletion.PROTECT, to='shop.product', verbose_name='producto' ), ), ( - "provider", + 'provider', models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="shop.provider", - verbose_name="proveedor", + to='shop.provider', + verbose_name='proveedor', ), ), ], - options={ - "verbose_name": "remesa de producto", - "verbose_name_plural": "remesas de productos", - }, + options={'verbose_name': 'remesa de producto', 'verbose_name_plural': 'remesas de productos'}, ), migrations.CreateModel( - name="ShippingMethod", + name='ShippingMethod', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=64, verbose_name='nombre')), + ('description', models.CharField(blank=True, default='', max_length=256, verbose_name='description')), + ('url', models.URLField(blank=True, default='', verbose_name='url')), + ('enabled', models.BooleanField(default=True, verbose_name='habilitado')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=64, verbose_name="nombre")), - ( - "description", - models.CharField( - blank=True, - default="", - max_length=256, - verbose_name="description", - ), - ), - ("url", models.URLField(blank=True, default="", verbose_name="url")), - ( - "enabled", - models.BooleanField(default=True, verbose_name="habilitado"), - ), - ( - "shipping_product", + 'shipping_product', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to="shop.product", - verbose_name="producto de envío", + to='shop.product', + verbose_name='producto de envío', ), ), ], - options={ - "verbose_name": "método de envío", - "verbose_name_plural": "métodos de envío", - }, + options={'verbose_name': 'método de envío', 'verbose_name_plural': 'métodos de envío'}, ), migrations.AddField( - model_name="order", - name="shipping_method", + model_name='order', + name='shipping_method', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - to="shop.shippingmethod", - verbose_name="método de envío", + to='shop.shippingmethod', + verbose_name='método de envío', ), ), migrations.AddField( - model_name="product", - name="tags", - field=models.ManyToManyField( - blank=True, to="shop.tag", verbose_name="etiquetas" - ), + model_name='product', + name='tags', + field=models.ManyToManyField(blank=True, to='shop.tag', verbose_name='etiquetas'), ), migrations.CreateModel( - name="ProductPrice", + name='ProductPrice', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), + ('price', models.DecimalField(decimal_places=2, max_digits=11, verbose_name='precio')), + ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='fecha')), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "price", + 'price_with_tax', models.DecimalField( - decimal_places=2, max_digits=11, verbose_name="precio" + blank=True, decimal_places=2, max_digits=11, null=True, verbose_name='precio con impuestos' ), ), + ('current', models.BooleanField(default=False, verbose_name='es el precio actual')), ( - "date", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="fecha" - ), - ), - ( - "price_with_tax", - models.DecimalField( - blank=True, - decimal_places=2, - max_digits=11, - null=True, - verbose_name="precio con impuestos", - ), - ), - ( - "current", - models.BooleanField( - default=False, verbose_name="es el precio actual" - ), - ), - ( - "product", + 'product', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - related_name="prices", - to="shop.product", - verbose_name="producto", + related_name='prices', + to='shop.product', + verbose_name='producto', ), ), ( - "tax", + 'tax', models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="shop.tax", - verbose_name="impuesto aplicable", + on_delete=django.db.models.deletion.PROTECT, to='shop.tax', verbose_name='impuesto aplicable' ), ), ], options={ - "verbose_name": "precio de producto", - "verbose_name_plural": "precio de producto", - "ordering": ("-date",), + 'verbose_name': 'precio de producto', + 'verbose_name_plural': 'precio de producto', + 'ordering': ('-date',), }, ), migrations.CreateModel( - name="WishlistedProduct", + name='WishlistedProduct', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='fecha de creación')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'last_modification_date', + models.DateTimeField(auto_now=True, verbose_name='fecha de última modificación'), ), ( - "creation_date", - models.DateTimeField( - auto_now_add=True, verbose_name="fecha de creación" - ), - ), - ( - "last_modification_date", - models.DateTimeField( - auto_now=True, verbose_name="fecha de última modificación" - ), - ), - ( - "product", + 'product', models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="shop.product", - verbose_name="producto", + on_delete=django.db.models.deletion.CASCADE, to='shop.product', verbose_name='producto' ), ), ( - "user", + 'user', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, - verbose_name="usuario", + verbose_name='usuario', ), ), ], options={ - "verbose_name": "productos deseados", - "verbose_name_plural": "productos deseados", - "unique_together": {("user", "product")}, + 'verbose_name': 'productos deseados', + 'verbose_name_plural': 'productos deseados', + 'unique_together': {('user', 'product')}, }, ), ] diff --git a/shop/migrations/0002_shopsettings_debug.py b/shop/migrations/0002_shopsettings_debug.py index cf0ced9..2ca28f5 100644 --- a/shop/migrations/0002_shopsettings_debug.py +++ b/shop/migrations/0002_shopsettings_debug.py @@ -4,15 +4,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - - dependencies = [ - ("shop", "0001_initial"), - ] + dependencies = [('shop', '0001_initial')] operations = [ migrations.AddField( - model_name="shopsettings", - name="debug", - field=models.BooleanField(default=True, verbose_name="Modo depuración"), - ), + model_name='shopsettings', + name='debug', + field=models.BooleanField(default=True, verbose_name='Modo depuración'), + ) ] diff --git a/shop/models.py b/shop/models.py index e43cfca..b32f2f6 100644 --- a/shop/models.py +++ b/shop/models.py @@ -19,12 +19,8 @@ User = get_user_model() class TimestampedModel(models.Model): - creation_date = models.DateTimeField( - auto_now_add=True, verbose_name=_("fecha de creación") - ) - last_modification_date = models.DateTimeField( - auto_now=True, verbose_name=_("fecha de última modificación") - ) + creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('fecha de creación')) + last_modification_date = models.DateTimeField(auto_now=True, verbose_name=_('fecha de última modificación')) class Meta: abstract = True @@ -37,33 +33,20 @@ class Tag(models.Model): return self.name class Meta: - verbose_name = _("etiqueta") - verbose_name_plural = _("etiquetas") - ordering = ("name",) + verbose_name = _('etiqueta') + verbose_name_plural = _('etiquetas') + ordering = ('name',) class ProductCategory(models.Model): name = models.CharField(max_length=32) parent = models.ForeignKey( - "shop.ProductCategory", - on_delete=models.SET_NULL, - null=True, - blank=True, - verbose_name=_("categoría padre"), - ) - promoted = models.BooleanField(default=False, verbose_name=_("promocionada")) - hidden = models.BooleanField(default=False, verbose_name=_("oculta")) - show_in_navbar = models.BooleanField( - default=False, verbose_name=_("mostrar en cabecera") - ) - slug = models.SlugField( - max_length=48, - blank=True, - default="", - verbose_name=_("slug"), - unique=True, - db_index=True, + 'shop.ProductCategory', on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('categoría padre') ) + promoted = models.BooleanField(default=False, verbose_name=_('promocionada')) + hidden = models.BooleanField(default=False, verbose_name=_('oculta')) + show_in_navbar = models.BooleanField(default=False, verbose_name=_('mostrar en cabecera')) + slug = models.SlugField(max_length=48, blank=True, default='', verbose_name=_('slug'), unique=True, db_index=True) def __str__(self): return self.name @@ -73,58 +56,24 @@ class ProductCategory(models.Model): super().save() class Meta: - verbose_name = _("categoría") - verbose_name_plural = _("categorías") - ordering = ("name",) + verbose_name = _('categoría') + verbose_name_plural = _('categorías') + ordering = ('name',) class Product(TimestampedModel): - sku = models.CharField( - max_length=64, - blank=False, - null=False, - unique=True, - verbose_name=_("código de referencia"), - ) - name = models.CharField( - max_length=96, - blank=False, - null=False, - verbose_name=_("nombre"), - ) - description = models.TextField( - blank=True, - default="", - verbose_name=_("descripción"), - ) - stock = models.DecimalField( - max_digits=13, - decimal_places=4, - default=Decimal("0"), - verbose_name=_("stock"), - ) - is_digital_asset = models.BooleanField( - default=False, verbose_name=_("es un activo digital") - ) - url = models.URLField(blank=True, verbose_name=_("URL de descarga")) - tags = models.ManyToManyField("shop.Tag", blank=True, verbose_name=_("etiquetas")) - categories = models.ManyToManyField( - "shop.ProductCategory", blank=True, verbose_name=_("categorías") - ) - brand = models.ForeignKey( - "shop.Brand", - blank=True, - null=True, - on_delete=models.SET_NULL, - verbose_name=_("marca"), - ) - slug = models.SlugField( - default="", blank=True, null=True, max_length=128, verbose_name=_("Slug") - ) - hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto")) - is_shipping_method = models.BooleanField( - default=False, verbose_name=_("es forma de envío") - ) + sku = models.CharField(max_length=64, blank=False, null=False, unique=True, verbose_name=_('código de referencia')) + name = models.CharField(max_length=96, blank=False, null=False, verbose_name=_('nombre')) + description = models.TextField(blank=True, default='', verbose_name=_('descripción')) + stock = models.DecimalField(max_digits=13, decimal_places=4, default=Decimal('0'), verbose_name=_('stock')) + is_digital_asset = models.BooleanField(default=False, verbose_name=_('es un activo digital')) + url = models.URLField(blank=True, verbose_name=_('URL de descarga')) + tags = models.ManyToManyField('shop.Tag', blank=True, verbose_name=_('etiquetas')) + categories = models.ManyToManyField('shop.ProductCategory', blank=True, verbose_name=_('categorías')) + brand = models.ForeignKey('shop.Brand', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('marca')) + slug = models.SlugField(default='', blank=True, null=True, max_length=128, verbose_name=_('Slug')) + hidden = models.BooleanField(default=False, db_index=True, verbose_name=_('oculto')) + is_shipping_method = models.BooleanField(default=False, verbose_name=_('es forma de envío')) def __str__(self): return self.name @@ -139,38 +88,25 @@ class Product(TimestampedModel): return self.prices.filter(current=True).first() class Meta: - verbose_name = _("producto") - verbose_name_plural = _("productos") - ordering = ("id",) + verbose_name = _('producto') + verbose_name_plural = _('productos') + ordering = ('id',) class ProductPrice(TimestampedModel): - price = models.DecimalField( - max_digits=11, decimal_places=2, verbose_name=_("precio") - ) - date = models.DateTimeField(default=timezone.now, verbose_name=_("fecha")) + price = models.DecimalField(max_digits=11, decimal_places=2, verbose_name=_('precio')) + date = models.DateTimeField(default=timezone.now, verbose_name=_('fecha')) product = models.ForeignKey( - "shop.Product", - on_delete=models.CASCADE, - verbose_name=_("producto"), - related_name="prices", - ) - tax = models.ForeignKey( - "shop.Tax", - on_delete=models.PROTECT, - verbose_name=_("impuesto aplicable"), + 'shop.Product', on_delete=models.CASCADE, verbose_name=_('producto'), related_name='prices' ) + tax = models.ForeignKey('shop.Tax', on_delete=models.PROTECT, verbose_name=_('impuesto aplicable')) price_with_tax = models.DecimalField( - max_digits=11, - decimal_places=2, - blank=True, - null=True, - verbose_name=_("precio con impuestos"), + max_digits=11, decimal_places=2, blank=True, null=True, verbose_name=_('precio con impuestos') ) - current = models.BooleanField(default=False, verbose_name=_("es el precio actual")) + current = models.BooleanField(default=False, verbose_name=_('es el precio actual')) def __str__(self): - return f"{self.price} - {self.tax.code}" + return f'{self.price} - {self.tax.code}' def save(self, *args, **kwargs): tax_value = self.price * Decimal(self.tax.value / 100) @@ -178,63 +114,41 @@ class ProductPrice(TimestampedModel): super().save() class Meta: - verbose_name = _("precio de producto") - verbose_name_plural = _("precio de producto") - ordering = ("-date",) + verbose_name = _('precio de producto') + verbose_name_plural = _('precio de producto') + ordering = ('-date',) class ProductImage(models.Model): product = models.ForeignKey( - "shop.Product", + 'shop.Product', on_delete=models.CASCADE, blank=False, null=False, - verbose_name=_("Producto"), - related_name="images", - ) - original = models.ImageField( - upload_to="uploads", blank=False, null=False, verbose_name=_("Imagen original") + verbose_name=_('Producto'), + related_name='images', ) + original = models.ImageField(upload_to='uploads', blank=False, null=False, verbose_name=_('Imagen original')) - xl = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("XL") - ) - xl_dark = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("XL (fondo oscuro)") - ) + xl = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('XL')) + xl_dark = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('XL (fondo oscuro)')) - l = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("L") - ) - l_dark = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("L (fondo oscuro)") - ) + l = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('L')) + l_dark = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('L (fondo oscuro)')) - m = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("M") - ) - m_dark = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("M (fondo oscuro)") - ) + m = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('M')) + m_dark = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('M (fondo oscuro)')) - s = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("S") - ) - s_dark = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("S (fondo oscuro)") - ) + s = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('S')) + s_dark = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('S (fondo oscuro)')) - xs = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("XS") - ) - xs_dark = models.ImageField( - upload_to="uploads", blank=True, null=True, verbose_name=_("XS (fondo oscuro)") - ) + xs = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('XS')) + xs_dark = models.ImageField(upload_to='uploads', blank=True, null=True, verbose_name=_('XS (fondo oscuro)')) def __str__(self): return self.original.url - def make_square(self, image, max_size=512, fill_color="#000000"): + def make_square(self, image, max_size=512, fill_color='#000000'): width, height = image.size if width > height: @@ -247,7 +161,7 @@ class ProductImage(models.Model): resized_image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) - new_image = Image.new("RGB", (max_size, max_size), fill_color) + new_image = Image.new('RGB', (max_size, max_size), fill_color) position_x = int((max_size - new_width) / 2) position_y = int((max_size - new_height) / 2) @@ -256,503 +170,328 @@ class ProductImage(models.Model): return new_image - def get_resized_image(self, image, size, fill_color="#000000"): + def get_resized_image(self, image, size, fill_color='#000000'): image = Image.open(image) image = self.make_square(image, size, fill_color) buffer = BytesIO() - image.save(fp=buffer, format="WEBP") + image.save(fp=buffer, format='WEBP') - return ContentFile(buffer.getvalue(), name=f"{self.product.slug}_{size}.webp") + return ContentFile(buffer.getvalue(), name=f'{self.product.slug}_{size}.webp') def save(self, *args, **kwargs): - self.xl = self.get_resized_image(self.original, 1024, fill_color="#FFFFFF") + self.xl = self.get_resized_image(self.original, 1024, fill_color='#FFFFFF') self.xl_dark = self.get_resized_image(self.original, 1024) - self.l = self.get_resized_image(self.original, 512, fill_color="#FFFFFF") + self.l = self.get_resized_image(self.original, 512, fill_color='#FFFFFF') self.l_dark = self.get_resized_image(self.original, 512) - self.m = self.get_resized_image(self.original, 256, fill_color="#FFFFFF") + self.m = self.get_resized_image(self.original, 256, fill_color='#FFFFFF') self.m_dark = self.get_resized_image(self.original, 256) - self.s = self.get_resized_image(self.original, 128, fill_color="#FFFFFF") + self.s = self.get_resized_image(self.original, 128, fill_color='#FFFFFF') self.s_dark = self.get_resized_image(self.original, 128) - self.xs = self.get_resized_image(self.original, 96, fill_color="#FFFFFF") + self.xs = self.get_resized_image(self.original, 96, fill_color='#FFFFFF') self.xs_dark = self.get_resized_image(self.original, 96) return super().save(*args, **kwargs) + class Meta: + verbose_name = _('imágenes de producto') + verbose_name_plural = _('imágenes de producto') + class ProductBatch(TimestampedModel): - code = models.CharField( - max_length=32, unique=True, db_index=True, verbose_name=_("código") - ) - product = models.ForeignKey( - "shop.Product", on_delete=models.PROTECT, verbose_name=_("producto") - ) - quantity = models.DecimalField( - max_digits=13, - decimal_places=4, - default=Decimal("1"), - verbose_name=_("cantidad"), - ) + code = models.CharField(max_length=32, unique=True, db_index=True, verbose_name=_('código')) + product = models.ForeignKey('shop.Product', on_delete=models.PROTECT, verbose_name=_('producto')) + quantity = models.DecimalField(max_digits=13, decimal_places=4, default=Decimal('1'), verbose_name=_('cantidad')) expiration_date = models.DateField( - blank=True, null=True, verbose_name=_("fecha de caducidad / consumo preferente") + blank=True, null=True, verbose_name=_('fecha de caducidad / consumo preferente') ) provider = models.ForeignKey( - "shop.Provider", - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name=_("proveedor"), + 'shop.Provider', on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('proveedor') ) def __str__(self): - return f"{self.code} - {self.product.name} - {self.quantity}" + return f'{self.code} - {self.product.name} - {self.quantity}' class Meta: - verbose_name = _("remesa de producto") - verbose_name_plural = _("remesas de productos") + verbose_name = _('remesa de producto') + verbose_name_plural = _('remesas de productos') class Tax(TimestampedModel): - code = models.CharField( - max_length=8, blank=False, unique=True, verbose_name=_("código de impuesto") - ) - value = models.PositiveIntegerField( - null=False, blank=False, verbose_name=_("valor entero (porcentaje)") - ) + code = models.CharField(max_length=8, blank=False, unique=True, verbose_name=_('código de impuesto')) + value = models.PositiveIntegerField(null=False, blank=False, verbose_name=_('valor entero (porcentaje)')) def __str__(self): - return f"{self.code} - {self.value}%" + return f'{self.code} - {self.value}%' class Meta: - verbose_name = _("impuesto") - verbose_name_plural = _("impuestos") - ordering = ("id",) + verbose_name = _('impuesto') + verbose_name_plural = _('impuestos') + ordering = ('id',) class Provider(TimestampedModel): - vat_id = models.CharField( - max_length=32, - blank=False, - unique=True, - verbose_name=_("documento de identidad"), - ) - name = models.CharField(max_length=64, blank=False, verbose_name=_("nombre")) + vat_id = models.CharField(max_length=32, blank=False, unique=True, verbose_name=_('documento de identidad')) + name = models.CharField(max_length=64, blank=False, verbose_name=_('nombre')) - email = models.EmailField(blank=False, verbose_name=_("e-mail de contacto")) - phone = models.CharField( - max_length=16, - blank=True, - null=True, - default="", - verbose_name=_("Teléfono de contacto"), - ) + email = models.EmailField(blank=False, verbose_name=_('e-mail de contacto')) + phone = models.CharField(max_length=16, blank=True, null=True, default='', verbose_name=_('Teléfono de contacto')) - address = models.CharField(max_length=255, blank=False, verbose_name=_("dirección")) - city = models.CharField(max_length=64, blank=False, verbose_name=_("ciudad")) - state = models.CharField(max_length=64, blank=False, verbose_name=_("región")) - country = models.CharField(max_length=64, blank=False, verbose_name=_("país")) - zip = models.CharField(max_length=32, blank=False, verbose_name=_("código postal")) + address = models.CharField(max_length=255, blank=False, verbose_name=_('dirección')) + city = models.CharField(max_length=64, blank=False, verbose_name=_('ciudad')) + state = models.CharField(max_length=64, blank=False, verbose_name=_('región')) + country = models.CharField(max_length=64, blank=False, verbose_name=_('país')) + zip = models.CharField(max_length=32, blank=False, verbose_name=_('código postal')) def __str__(self): - return f"{self.vat_id} - {self.name}" + return f'{self.vat_id} - {self.name}' class Meta: - verbose_name = _("proveedor") - verbose_name_plural = _("proveedores") + verbose_name = _('proveedor') + verbose_name_plural = _('proveedores') class Brand(TimestampedModel): - name = models.CharField( - max_length=100, unique=True, blank=False, null=False, verbose_name=_("nombre") - ) + name = models.CharField(max_length=100, unique=True, blank=False, null=False, verbose_name=_('nombre')) class Meta: - verbose_name = _("marca") - verbose_name_plural = _("marcas") + verbose_name = _('marca') + verbose_name_plural = _('marcas') class OrderLine(TimestampedModel): - order = models.ForeignKey( - "shop.Order", - on_delete=models.CASCADE, - related_name="lines", - verbose_name=_("pedido"), - ) + order = models.ForeignKey('shop.Order', on_delete=models.CASCADE, related_name='lines', verbose_name=_('pedido')) product = models.ForeignKey( - "shop.Product", - on_delete=models.CASCADE, - null=False, - blank=False, - verbose_name=_("producto"), + 'shop.Product', on_delete=models.CASCADE, null=False, blank=False, verbose_name=_('producto') ) quantity = models.DecimalField( - max_digits=13, - decimal_places=4, - default=Decimal("1"), - null=False, - blank=False, - verbose_name=_("cantidad"), + max_digits=13, decimal_places=4, default=Decimal('1'), null=False, blank=False, verbose_name=_('cantidad') ) # Precio del producto en el momento en el que se crea el pedido - price = models.DecimalField( - max_digits=13, decimal_places=4, verbose_name=_("precio") - ) + price = models.DecimalField(max_digits=13, decimal_places=4, verbose_name=_('precio')) # base_total = quantity * price base_total = models.DecimalField( max_digits=13, decimal_places=4, - default=Decimal("0"), + default=Decimal('0'), null=False, blank=False, - verbose_name=_("total sin impuestos"), + verbose_name=_('total sin impuestos'), ) # Valor entero del impuesto en el momento en el que se crea el pedido - tax_value = models.PositiveIntegerField( - null=False, blank=False, verbose_name=_("valor de impuestos") - ) + tax_value = models.PositiveIntegerField(null=False, blank=False, verbose_name=_('valor de impuestos')) # taxes = price * quantity * (tax_value / 100) - taxes = models.DecimalField( - max_digits=13, decimal_places=4, verbose_name=_("impuestos") - ) + taxes = models.DecimalField(max_digits=13, decimal_places=4, verbose_name=_('impuestos')) # total = base_total + taxes - total = models.DecimalField( - max_digits=13, decimal_places=4, verbose_name=_("total") - ) + total = models.DecimalField(max_digits=13, decimal_places=4, verbose_name=_('total')) def __str__(self): - return f"{self.product.name} - {self.quantity} - {self.price}" + return f'{self.product.name} - {self.quantity} - {self.price}' class Meta: - verbose_name = _("línea de pedido") - verbose_name_plural = _("líneas de pedido") + verbose_name = _('línea de pedido') + verbose_name_plural = _('líneas de pedido') def create_order_code(*args, **kwargs): - return ( - timezone.now() - .isoformat() - .replace("-", "") - .replace("T", "") - .replace(":", "") - .replace(".", "")[:-5] - ) + return timezone.now().isoformat().replace('-', '').replace('T', '').replace(':', '').replace('.', '')[:-5] class Order(TimestampedModel): class Statuses(models.TextChoices): - STATUS_PENDING = "PEN", _("pendiente de pago") - STATUS_PAID = "PAI", _("pagado") - STATUS_RETURNED = "RTN", _("devuelto") - STATUS_ERROR = "ERR", _("Error") + STATUS_PENDING = 'PEN', _('pendiente de pago') + STATUS_PAID = 'PAI', _('pagado') + STATUS_RETURNED = 'RTN', _('devuelto') + STATUS_ERROR = 'ERR', _('Error') class ShippingStatuses(models.TextChoices): - STATUS_NOT_READY = "NOT", _("no preparado") - STATUS_READY = "RDY", _("preparado") - STATUS_TO_BE_SENT = "TBS", _("listo para ser enviado") - STATUS_SENT = "SNT", _("enviado") - STATUS_DELIVERED = "DLV", _("entregado") - STATUS_FINISHED = "FIN", _("finalizado") - STATUS_CANCELED = "CAN", _("cancelado") - STATUS_REQUESTED_RETURN = "RQT", _("devolución solicitada") - STATUS_RETURNED = "RTN", _("devuelto") + STATUS_NOT_READY = 'NOT', _('no preparado') + STATUS_READY = 'RDY', _('preparado') + STATUS_TO_BE_SENT = 'TBS', _('listo para ser enviado') + STATUS_SENT = 'SNT', _('enviado') + STATUS_DELIVERED = 'DLV', _('entregado') + STATUS_FINISHED = 'FIN', _('finalizado') + STATUS_CANCELED = 'CAN', _('cancelado') + STATUS_REQUESTED_RETURN = 'RQT', _('devolución solicitada') + STATUS_RETURNED = 'RTN', _('devuelto') - uuid = models.UUIDField(default=uuid4, verbose_name=_("UUID"), db_index=True) + uuid = models.UUIDField(default=uuid4, verbose_name=_('UUID'), db_index=True) code = models.CharField(default=create_order_code, max_length=20, unique=True) - status = models.CharField( - max_length=3, default=Statuses.STATUS_PENDING, verbose_name=_("estado") - ) + status = models.CharField(max_length=3, default=Statuses.STATUS_PENDING, verbose_name=_('estado')) shipping_status = models.CharField( - max_length=3, - default=ShippingStatuses.STATUS_NOT_READY, - verbose_name=_("estado de envío"), + max_length=3, default=ShippingStatuses.STATUS_NOT_READY, verbose_name=_('estado de envío') ) base_total = models.DecimalField( - default=Decimal("0"), - max_digits=13, - decimal_places=2, - verbose_name=_("base imponible"), + default=Decimal('0'), max_digits=13, decimal_places=2, verbose_name=_('base imponible') ) - total = models.DecimalField( - default=Decimal("0"), max_digits=13, decimal_places=2, verbose_name=_("total") - ) + total = models.DecimalField(default=Decimal('0'), max_digits=13, decimal_places=2, verbose_name=_('total')) - user = models.ForeignKey( - User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("cliente") - ) - email = models.EmailField(blank=True, verbose_name=_("e-mail")) + user = models.ForeignKey(User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('cliente')) + email = models.EmailField(blank=True, verbose_name=_('e-mail')) # Datos de facturación - billing_address = models.CharField( - max_length=255, blank=False, verbose_name=_("dirección de facturación") - ) - billing_city = models.CharField( - max_length=64, blank=False, verbose_name=_("ciudad de facturación") - ) - billing_state = models.CharField( - max_length=64, blank=False, verbose_name=_("región de facturación") - ) - billing_country = models.CharField( - max_length=64, blank=False, verbose_name=_("país de facturación") - ) - billing_zip = models.CharField( - max_length=32, blank=False, verbose_name=_("código postal de facturación") - ) + billing_address = models.CharField(max_length=255, blank=False, verbose_name=_('dirección de facturación')) + billing_city = models.CharField(max_length=64, blank=False, verbose_name=_('ciudad de facturación')) + billing_state = models.CharField(max_length=64, blank=False, verbose_name=_('región de facturación')) + billing_country = models.CharField(max_length=64, blank=False, verbose_name=_('país de facturación')) + billing_zip = models.CharField(max_length=32, blank=False, verbose_name=_('código postal de facturación')) # Datos de envío - shipping_address = models.CharField( - max_length=255, blank=False, verbose_name=_("dirección") - ) - shipping_city = models.CharField( - max_length=64, blank=False, verbose_name=_("ciudad") - ) - shipping_state = models.CharField( - max_length=64, blank=False, verbose_name=_("región") - ) - shipping_country = models.CharField( - max_length=64, blank=False, verbose_name=_("país") - ) - shipping_zip = models.CharField( - max_length=32, blank=False, verbose_name=_("código postal") - ) - contact_phone = models.CharField( - max_length=32, blank=True, verbose_name=_("teléfono de contacto") - ) + shipping_address = models.CharField(max_length=255, blank=False, verbose_name=_('dirección')) + shipping_city = models.CharField(max_length=64, blank=False, verbose_name=_('ciudad')) + shipping_state = models.CharField(max_length=64, blank=False, verbose_name=_('región')) + shipping_country = models.CharField(max_length=64, blank=False, verbose_name=_('país')) + shipping_zip = models.CharField(max_length=32, blank=False, verbose_name=_('código postal')) + contact_phone = models.CharField(max_length=32, blank=True, verbose_name=_('teléfono de contacto')) amount_paid = models.DecimalField( - default=Decimal("0.00"), - max_digits=11, - decimal_places=2, - verbose_name=_("cantidad pagada"), + default=Decimal('0.00'), max_digits=11, decimal_places=2, verbose_name=_('cantidad pagada') ) shipping_method = models.ForeignKey( - "shop.ShippingMethod", - blank=True, - null=True, - on_delete=models.SET_NULL, - verbose_name=_("método de envío"), + 'shop.ShippingMethod', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('método de envío') ) from_cart = models.ForeignKey( - "shop.Cart", - on_delete=models.SET_NULL, - blank=True, - null=True, - verbose_name=_("carrito de origen de pedido"), + 'shop.Cart', on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('carrito de origen de pedido') ) def calculate_total_from_lines(self): - self.base_total = self.lines.aggregate(base_total=models.Sum("base_total")).get( - "base_total" - ) - self.total = self.lines.aggregate(base_total=models.Sum("total")).get( - "base_total" - ) + self.base_total = self.lines.aggregate(base_total=models.Sum('base_total')).get('base_total') + self.total = self.lines.aggregate(base_total=models.Sum('total')).get('base_total') self.save() class Meta: - verbose_name = _("pedido") - verbose_name_plural = _("pedidos") - ordering = ("creation_date",) + verbose_name = _('pedido') + verbose_name_plural = _('pedidos') + ordering = ('creation_date',) class Cart(models.Model): - uuid = models.UUIDField(default=uuid4, primary_key=True, verbose_name=_("uuid")) - user = models.ForeignKey( - User, - on_delete=models.CASCADE, - verbose_name=_("usuario"), - blank=True, - null=True, - ) - creation_date = models.DateTimeField( - auto_now_add=True, verbose_name=_("fecha de creación") - ) + uuid = models.UUIDField(default=uuid4, primary_key=True, verbose_name=_('uuid')) + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('usuario'), blank=True, null=True) + creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('fecha de creación')) class Meta: - verbose_name = _("carrito") - verbose_name_plural = _("carritos") + verbose_name = _('carrito') + verbose_name_plural = _('carritos') class CartItem(models.Model): - cart = models.ForeignKey( - "shop.Cart", - on_delete=models.CASCADE, - related_name="items", - verbose_name=_("carrito"), - ) - product = models.ForeignKey( - "shop.Product", on_delete=models.CASCADE, verbose_name=_("producto") - ) - quantity = models.PositiveIntegerField(default=1, verbose_name=_("cantidad")) + cart = models.ForeignKey('shop.Cart', on_delete=models.CASCADE, related_name='items', verbose_name=_('carrito')) + product = models.ForeignKey('shop.Product', on_delete=models.CASCADE, verbose_name=_('producto')) + quantity = models.PositiveIntegerField(default=1, verbose_name=_('cantidad')) class Meta: - verbose_name = _("línea de carrito") - verbose_name_plural = _("líneas de carrito") + verbose_name = _('línea de carrito') + verbose_name_plural = _('líneas de carrito') class CustomerAddress(models.Model): class Types(models.TextChoices): - BILLING = "BILL", _("facturación") - SHIPPING = "SHIP", _("envío") + BILLING = 'BILL', _('facturación') + SHIPPING = 'SHIP', _('envío') - user = models.ForeignKey( - User, on_delete=models.CASCADE, null=True, verbose_name=_("usuario") - ) + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name=_('usuario')) vat_id = models.CharField( - max_length=16, - blank=True, - null=True, - default="", - verbose_name=_("número de identificación fiscal"), - ) - full_name = models.CharField( - max_length=128, blank=False, null=False, verbose_name=_("dirección") - ) - address = models.CharField( - max_length=128, blank=False, null=False, verbose_name=_("dirección") - ) - address_town = models.CharField( - max_length=64, blank=False, null=False, verbose_name=_("localidad") - ) - address_zip = models.CharField( - max_length=16, blank=False, null=False, verbose_name=_("código postal") - ) - address_state = models.CharField( - max_length=64, blank=False, null=False, verbose_name=_("provincia") + max_length=16, blank=True, null=True, default='', verbose_name=_('número de identificación fiscal') ) + full_name = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección')) + address = models.CharField(max_length=128, blank=False, null=False, verbose_name=_('dirección')) + address_town = models.CharField(max_length=64, blank=False, null=False, verbose_name=_('localidad')) + address_zip = models.CharField(max_length=16, blank=False, null=False, verbose_name=_('código postal')) + address_state = models.CharField(max_length=64, blank=False, null=False, verbose_name=_('provincia')) address_country = models.CharField( - max_length=64, - default=settings.SHOP_COUNTRY, - blank=False, - null=False, - verbose_name=_("país"), - ) - address_phone = models.CharField( - max_length=16, blank=True, null=True, default="", verbose_name=_("teléfono") + max_length=64, default=settings.SHOP_COUNTRY, blank=False, null=False, verbose_name=_('país') ) + address_phone = models.CharField(max_length=16, blank=True, null=True, default='', verbose_name=_('teléfono')) address_type = models.CharField( - max_length=4, - choices=Types.choices, - default=Types.SHIPPING, - verbose_name=_("tipo de dirección"), + max_length=4, choices=Types.choices, default=Types.SHIPPING, verbose_name=_('tipo de dirección') ) - email = models.EmailField(blank=False, null=False, verbose_name=_("e-mail")) - default = models.BooleanField(default=True, verbose_name=_("por defecto")) - hidden = models.BooleanField(default=False, db_index=True, verbose_name=_("oculto")) + email = models.EmailField(blank=False, null=False, verbose_name=_('e-mail')) + default = models.BooleanField(default=True, verbose_name=_('por defecto')) + hidden = models.BooleanField(default=False, db_index=True, verbose_name=_('oculto')) def __str__(self): - return f"{self.full_name} - {self.address}, {self.address_zip}, {self.address_state} - {self.address_country}" + return f'{self.full_name} - {self.address}, {self.address_zip}, {self.address_state} - {self.address_country}' class Meta: - verbose_name = _("dirección de cliente") - verbose_name_plural = _("direcciones de cliente") + verbose_name = _('dirección de cliente') + verbose_name_plural = _('direcciones de cliente') class ShippingMethod(models.Model): - name = models.CharField(max_length=64, verbose_name=_("nombre")) - description = models.CharField( - max_length=256, default="", blank=True, verbose_name=_("description") - ) - url = models.URLField(default="", blank=True, verbose_name=_("url")) - shipping_product = models.ForeignKey( - "shop.Product", on_delete=models.CASCADE, verbose_name=_("producto de envío") - ) - enabled = models.BooleanField(default=True, verbose_name=_("habilitado")) + name = models.CharField(max_length=64, verbose_name=_('nombre')) + description = models.CharField(max_length=256, default='', blank=True, verbose_name=_('description')) + url = models.URLField(default='', blank=True, verbose_name=_('url')) + shipping_product = models.ForeignKey('shop.Product', on_delete=models.CASCADE, verbose_name=_('producto de envío')) + enabled = models.BooleanField(default=True, verbose_name=_('habilitado')) class Meta: - verbose_name = _("método de envío") - verbose_name_plural = _("métodos de envío") + verbose_name = _('método de envío') + verbose_name_plural = _('métodos de envío') class WishlistedProduct(TimestampedModel): - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_("usuario")) - product = models.ForeignKey( - "shop.Product", on_delete=models.CASCADE, verbose_name=_("producto") - ) + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('usuario')) + product = models.ForeignKey('shop.Product', on_delete=models.CASCADE, verbose_name=_('producto')) class Meta: - verbose_name = _("productos deseados") - verbose_name_plural = _("productos deseados") - unique_together = ( - "user", - "product", - ) + verbose_name = _('productos deseados') + verbose_name_plural = _('productos deseados') + unique_together = ('user', 'product') class Payment(models.Model): class MethodChoices(models.TextChoices): - REDSYS = "REDSYS" + REDSYS = 'REDSYS' - hash = models.UUIDField(verbose_name=_("Hash"), default=uuid4, primary_key=True) - amount = models.DecimalField( - max_digits=8, - decimal_places=2, - default=Decimal("0"), - verbose_name=_("Cantidad"), - ) + hash = models.UUIDField(verbose_name=_('Hash'), default=uuid4, primary_key=True) + amount = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal('0'), verbose_name=_('Cantidad')) order = models.ForeignKey( - "shop.Order", - on_delete=models.PROTECT, - verbose_name=_("pedido"), - related_name="payments", - ) - user = models.ForeignKey( - User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("cliente") + 'shop.Order', on_delete=models.PROTECT, verbose_name=_('pedido'), related_name='payments' ) + user = models.ForeignKey(User, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_('cliente')) - creation_date = models.DateTimeField( - auto_now_add=True, verbose_name=_("Fecha de creación") - ) + creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Fecha de creación')) - metadata = models.JSONField(default=dict, verbose_name=_("Metadata")) + metadata = models.JSONField(default=dict, verbose_name=_('Metadata')) method = models.CharField( - max_length=6, - choices=MethodChoices.choices, - default=MethodChoices.REDSYS, - verbose_name=_("método de pago"), + max_length=6, choices=MethodChoices.choices, default=MethodChoices.REDSYS, verbose_name=_('método de pago') ) class Meta: - verbose_name = _("Pago") - verbose_name_plural = _("Pagos") + verbose_name = _('Pago') + verbose_name_plural = _('Pagos') class ShopSettings(SingletonModel): - debug = models.BooleanField(default=True, verbose_name=_("Modo depuración")) + debug = models.BooleanField(default=True, verbose_name=_('Modo depuración')) merchant_code = models.CharField( - max_length=9, - blank=False, - null=False, - verbose_name=_("Identificación de comercio"), + max_length=9, blank=False, null=False, verbose_name=_('Identificación de comercio') ) currency_code = models.CharField( - max_length=4, - default="978", - choices=CURRENCY_CODES, - verbose_name=_("Código de moneda"), + max_length=4, default='978', choices=CURRENCY_CODES, verbose_name=_('Código de moneda') ) # 978 == EURO - terminal = models.CharField(max_length=8, verbose_name=_("Terminal")) - shared_secret = models.CharField(max_length=100, verbose_name=_("Clave de Redsys")) - tpv_domain = models.URLField( - max_length=128, blank=True, null=True, verbose_name=_("dominio del tpv") - ) + terminal = models.CharField(max_length=8, verbose_name=_('Terminal')) + shared_secret = models.CharField(max_length=100, verbose_name=_('Clave de Redsys')) + tpv_domain = models.URLField(max_length=128, blank=True, null=True, verbose_name=_('dominio del tpv')) class Meta: - verbose_name = _("ajustes de la tienda") - verbose_name_plural = _("ajustes de la tienda") + verbose_name = _('ajustes de la tienda') + verbose_name_plural = _('ajustes de la tienda') diff --git a/shop/redsys.py b/shop/redsys.py index 93378b5..e4add0c 100644 --- a/shop/redsys.py +++ b/shop/redsys.py @@ -13,13 +13,11 @@ from shop.utils import compute_signature, decode_b64_dict class RedsysClient: - DEBUG_ENVIRONMENT_URL = "https://sis-t.redsys.es:25443/sis/realizarPago" - PROD_ENVIRONMENT_URL = "https://sis.redsys.es/sis/realizarPago" + DEBUG_ENVIRONMENT_URL = 'https://sis-t.redsys.es:25443/sis/realizarPago' + PROD_ENVIRONMENT_URL = 'https://sis.redsys.es/sis/realizarPago' - REST_DEBUG_ENVIRONMENT_URL = ( - "https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST" - ) - REST_PROD_ENVIRONMENT_URL = "https://sis.redsys.es/sis/rest/trataPeticionREST" + REST_DEBUG_ENVIRONMENT_URL = 'https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST' + REST_PROD_ENVIRONMENT_URL = 'https://sis.redsys.es/sis/rest/trataPeticionREST' def __init__(self): self.settings = ShopSettings.load() @@ -46,19 +44,19 @@ class RedsysClient: return self.settings.currency_code def get_merchant_url_ok_for_order(self, order: Order) -> str: - path = reverse("web:order", kwargs={"uuid": order.uuid}) - return f"{self.settings.tpv_domain}{path}" + path = reverse('web:order', kwargs={'uuid': order.uuid}) + return f'{self.settings.tpv_domain}{path}' def to_integer(self, value: Decimal): return int(value * 100) def get_merchant_url_ko_for_order(self, order: Order) -> str: - path = reverse("web:order", kwargs={"uuid": order.uuid}) - return f"{self.settings.tpv_domain}{path}" + path = reverse('web:order', kwargs={'uuid': order.uuid}) + return f'{self.settings.tpv_domain}{path}' def get_webhook_url_for_order(self, order: Order) -> str: - path = reverse("shop:webhook", kwargs={"uuid": order.uuid}) - return f"{self.settings.tpv_domain}{path}" + path = reverse('shop:webhook', kwargs={'uuid': order.uuid}) + return f'{self.settings.tpv_domain}{path}' def get_shared_secret(self) -> str: return self.settings.shared_secret @@ -68,131 +66,89 @@ class RedsysClient: return compute_signature(str(hash), payload, key).decode() def _get_merchant_parameters_for_order( - self, - order: Order, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, order_type: int = TransactionTypes.AUTHORIZATION ) -> dict: merchant_code = self.get_merchant_code() return { - "DS_MERCHANT_AMOUNT": str(self.to_integer(order.total)), - "DS_MERCHANT_CURRENCY": self.get_currency_code(), - "DS_MERCHANT_MERCHANTCODE": merchant_code, + 'DS_MERCHANT_AMOUNT': str(self.to_integer(order.total)), + 'DS_MERCHANT_CURRENCY': self.get_currency_code(), + 'DS_MERCHANT_MERCHANTCODE': merchant_code, # Webhook - "DS_MERCHANT_MERCHANTURL": self.get_webhook_url_for_order(order), - "DS_MERCHANT_ORDER": order.code, - "DS_MERCHANT_TERMINAL": self.get_terminal(), - "DS_MERCHANT_TRANSACTIONTYPE": order_type, + 'DS_MERCHANT_MERCHANTURL': self.get_webhook_url_for_order(order), + 'DS_MERCHANT_ORDER': order.code, + 'DS_MERCHANT_TERMINAL': self.get_terminal(), + 'DS_MERCHANT_TRANSACTIONTYPE': order_type, # Página informativa al usuario - Pago erróneo - "DS_MERCHANT_URLKO": self.get_merchant_url_ko_for_order(order), + 'DS_MERCHANT_URLKO': self.get_merchant_url_ko_for_order(order), # Página informativa al usuario - Pago confirmado - "DS_MERCHANT_URLOK": self.get_merchant_url_ok_for_order(order), + 'DS_MERCHANT_URLOK': self.get_merchant_url_ok_for_order(order), } def _get_encoded_merchant_parameters_for_order( - self, - order: Order, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, order_type: int = TransactionTypes.AUTHORIZATION ) -> str: body = self._get_merchant_parameters_for_order(order, order_type) return self._encode_body(body) - def get_body_for_order( - self, - order: Order, - order_type: int = TransactionTypes.AUTHORIZATION, - ) -> dict: - merchant_parameters = self._get_encoded_merchant_parameters_for_order( - order, order_type - ) + def get_body_for_order(self, order: Order, order_type: int = TransactionTypes.AUTHORIZATION) -> dict: + merchant_parameters = self._get_encoded_merchant_parameters_for_order(order, order_type) signature = self.get_signature(order.code, merchant_parameters) return { - "Ds_MerchantParameters": merchant_parameters, - "Ds_SignatureVersion": "HMAC_SHA256_V1", - "Ds_Signature": signature, + 'Ds_MerchantParameters': merchant_parameters, + 'Ds_SignatureVersion': 'HMAC_SHA256_V1', + 'Ds_Signature': signature, } def _encode_body(self, value): stringified_body = json.dumps(value) - return base64.b64encode(stringified_body.encode()).decode("utf-8") + return base64.b64encode(stringified_body.encode()).decode('utf-8') def _get_rest_merchant_parameters_for_order( - self, - order: Order, - pan: str, - expiry_date: str, - cvv2: str, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, pan: str, expiry_date: str, cvv2: str, order_type: int = TransactionTypes.AUTHORIZATION ) -> dict: merchant_code = self.get_merchant_code() return { - "DS_MERCHANT_AMOUNT": str(self.to_integer(order.total)), - "DS_MERCHANT_CURRENCY": self.get_currency_code(), - "DS_MERCHANT_CVV2": cvv2, - "DS_MERCHANT_EXPIRYDATE": expiry_date, - "DS_MERCHANT_MERCHANTCODE": merchant_code, - "DS_MERCHANT_ORDER": order.uuid.hex, - "DS_MERCHANT_PAN": pan, - "DS_MERCHANT_TERMINAL": "1", - "DS_MERCHANT_TRANSACTIONTYPE": order_type, + 'DS_MERCHANT_AMOUNT': str(self.to_integer(order.total)), + 'DS_MERCHANT_CURRENCY': self.get_currency_code(), + 'DS_MERCHANT_CVV2': cvv2, + 'DS_MERCHANT_EXPIRYDATE': expiry_date, + 'DS_MERCHANT_MERCHANTCODE': merchant_code, + 'DS_MERCHANT_ORDER': order.uuid.hex, + 'DS_MERCHANT_PAN': pan, + 'DS_MERCHANT_TERMINAL': '1', + 'DS_MERCHANT_TRANSACTIONTYPE': order_type, } def _get_rest_encoded_merchant_parameters_for_order( - self, - order: Order, - pan: str, - expiry_date: str, - cvv2: str, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, pan: str, expiry_date: str, cvv2: str, order_type: int = TransactionTypes.AUTHORIZATION ) -> str: body = self._get_rest_merchant_parameters_for_order( - order, - pan=pan, - expiry_date=expiry_date, - cvv2=cvv2, - order_type=order_type, + order, pan=pan, expiry_date=expiry_date, cvv2=cvv2, order_type=order_type ) return self._encode_body(body) def _get_rest_body_for_order( - self, - order: Order, - pan: str, - expiry_date: str, - cvv2: str, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, pan: str, expiry_date: str, cvv2: str, order_type: int = TransactionTypes.AUTHORIZATION ) -> dict: merchant_parameters = self._get_rest_encoded_merchant_parameters_for_order( - order=order, - pan=pan, - expiry_date=expiry_date, - cvv2=cvv2, - order_type=order_type, + order=order, pan=pan, expiry_date=expiry_date, cvv2=cvv2, order_type=order_type ) signature = self.get_signature(order.code, merchant_parameters) return { - "Ds_MerchantParameters": merchant_parameters, - "Ds_SignatureVersion": "HMAC_SHA256_V1", - "Ds_Signature": signature, + 'Ds_MerchantParameters': merchant_parameters, + 'Ds_SignatureVersion': 'HMAC_SHA256_V1', + 'Ds_Signature': signature, } def make_request_for_order( - self, - order: Order, - pan: str, - expiry_date: str, - cvv2: str, - order_type: int = TransactionTypes.AUTHORIZATION, + self, order: Order, pan: str, expiry_date: str, cvv2: str, order_type: int = TransactionTypes.AUTHORIZATION ): body = self._get_rest_body_for_order( - order=order, - pan=pan, - expiry_date=expiry_date, - cvv2=cvv2, - order_type=order_type, + order=order, pan=pan, expiry_date=expiry_date, cvv2=cvv2, order_type=order_type ) url = self.get_rest_target_url() response = requests.post(url, body) @@ -201,14 +157,14 @@ class RedsysClient: def pay_order_rest(self, order: Order, pan: str, expiry_date: str, cvv2: str): response = self.make_request_for_order(order, pan, expiry_date, cvv2) data = response.json() - error_code: str = data.get("errorCode", "") + error_code: str = data.get('errorCode', '') if error_code: - error_code.replace("SIS0", "0") + error_code.replace('SIS0', '0') raise RedsysPaymentException(error_code) - merchant_parameters = data.get("Ds_MerchantParameters") + merchant_parameters = data.get('Ds_MerchantParameters') parameters = decode_b64_dict(merchant_parameters) return parameters diff --git a/shop/settings.py b/shop/settings.py index 185a6d2..cef0983 100644 --- a/shop/settings.py +++ b/shop/settings.py @@ -20,253 +20,249 @@ class TransactionTypes: CURRENCY_CODES = { - "8": "LEK ALL", - "12": "ALGERIAN DINAR DZD", - "24": "ANGOLA KWANZA AOK", - "30": "PROBANDO DESA MON", - "31": "AZERBAIJANIAN MANAT AZM", - "32": "ARGENTINE AUSTRAL ARP", - "36": "AUSTRALIAN DOLLAR AUD", - "44": "BAHAMIAN DOLLAR BSD", - "48": "BAHRAINI DINAR BHD", - "50": "TAKA BDT", - "51": "ARMENIAN DRAM AMD", - "52": "BARBADOS DOLLAR BBD", - "60": "BERMUDAN DOLLAR BMD", - "64": "NGULTRUM BTN", - "68": "BOLIVIAN PESO BOP", - "70": "DINAR BAD", - "72": "PULA BWP", - "76": "CRUZEIRO BRC", - "84": "BELIZE DOLLAR 084", - "90": "SOLOMON ISLANDS DOLL SBD", - "96": "BRUNEI DOLLAR BND", - "100": "LEV BGL", - "104": "KYAT BUK", - "108": "BURUNDI FRANC BIF", - "112": "BELARUSSIAN RUBLE BYB", - "116": "RIEL KHR", - "124": "CANADIAN DOLLAR CAD", - "132": "CAPE VERDE ESCUDO CVE", - "136": "CAYMAN ISLANDS DOLLA KYD", - "144": "SRI LANKA RUPEE LKR", - "152": "CHILEAN PESO CLP", - "156": "YUAN RENMINBI CNY", - "157": "CHINESE RENMIMBI CNH", - "158": "CHINESE RENMINBI CNX", - "170": "COLOMBIAN PESO COP", - "174": "COMOROS FRANC KMF", - "180": "ZAIRE ZRZ", - "188": "COSTA RICA COLON CRC", - "191": "CROATIAN KUNA HRK", - "192": "CUBAN PESO CUP", - "196": "CYPRUS POUND CYP", - "200": "KORUNA CSK", - "203": "CZECH KORUNA CZK", - "208": "DANISH KRONE DKK", - "214": "DOMINICAN PESO DOP", - "218": "SUCRE ECS", - "222": "EL SALVADOR COLON SVC", - "226": "EKWELE GQE", - "230": "ETHIOPIAN BIRR ETB", - "232": "ERITREAN NAKTAN ERN", - "233": "ESTONIAN KROON EEK", - "238": "FALKLAND ISLANDS FKP", - "242": "FIJI DOLLAR FJD", - "262": "DJIBOUTI FRANC DJF", - "268": "GEORGIAN LARI GEL", - "270": "DALASI GMD", - "278": "MARK DER DDR DDM", - "288": "GHANA CEDI GHC", - "292": "GIBRALTAR POUND GIP", - "320": "QUETZAL GTQ", - "324": "SYLI GNS", - "328": "GUYANA DOLLAR GYD", - "332": "GOURDE HTG", - "340": "LEMPIRA HNL", - "344": "HONG KONG DOLLAR HKD", - "348": "FORINT HUF", - "352": "ICELAND KRONA ISK", - "356": "INDIAN RUPEE INR", - "360": "RUPIAH IDR", - "364": "IRANIAL RIAL IRR", - "365": "IRANIAN AIRLINE RATE IRA", - "368": "IRAQI DINAR IQD", - "376": "ISRAEL SHEKEL ILS", - "388": "JAMAICAN DOLLAR JMD", - "392": "YEN JPY", - "398": "TENGE KZT", - "400": "JORDANIAN DINAR JOD", - "404": "KENYAN SHILLING KES", - "408": "NORTH KOREAN WON KPW", - "410": "KOREAN WON KRW", - "414": "KUWAITI DINAR KWD", - "417": "KYRGYZSTAN SON KGS", - "418": "KIP LAK", - "422": "LEBANESE POUND LBP", - "426": "LESOTHO LOTI LSM", - "428": "LATVIAN LAT LVL", - "430": "LIBERIAN DOLLAR LRD", - "434": "LIBYAN DINAR LYD", - "440": "LITHUANIAN LITAS LTL", - "446": "PATACA MOP", - "450": "MALAGASY FRANC MGF", - "454": "MALAWI KWACHA MWK", - "458": "MALASYAN RINGGIT MYR", - "462": "MALDIVE RUPEE MVR", - "466": "MALI MLF", - "470": "MALTESE LIRA MTL", - "478": "OUGUIYA MRO", - "480": "MAURITIUS RUPEE MUR", - "484": "MEXICAN PESO MXP", - "496": "TUGRIK MNT", - "498": "MOLDOVIAN LEU MDL", - "504": "MORROCAN DIRHAM MAD", - "508": "METICAL MZM", - "512": "RIAL OMANI OMR", - "516": "NAMIBIAN DOLLAR NAD", - "524": "NEPALESE RUPEE NPR", - "532": "NETHERLANDS ANTILLIA ANG", - "533": "ARUBA AWG", - "536": "YUGOSLAVIAN NEW DIAN NTZ", - "548": "VANUATU VATU VUV", - "554": "NEW ZEALAND DOLLAR NZD", - "556": "NAIRA 566", - "558": "CORDOBA NIC", - "566": "NAIRA NGN", - "578": "NORWEGIAN KRONE NOK", - "582": "PACIFIC ISLAND PCI", - "586": "PAKISTAN RUPEE PKR", - "590": "BALBOA PAB", - "598": "KINA PGK", - "600": "GUARANI PYG", - "604": "PERU INTI PEI", - "608": "PHILIPPINE PESO PHP", - "616": "ZLOTY PLZ", - "624": "GUINEA", - "626": "TIMOR ESCUDO TPE", - "634": "QATARI RIAL QAR", - "642": "LEU ROL", - "643": "RUSSIAN ROUBLE RUB", - "646": "RWANDA FRANC RWF", - "654": "ST.HELENA POUND SHP", - "678": "DOBRA STD", - "682": "SAUDI RIYAL SAR", - "690": "SEYCHELLES RUPEE SCR", - "694": "LEONE SLL", - "702": "SINGAPORE DOLLAR SGD", - "703": "SLOVAK KORUNA SKK", - "704": "DONG VND", - "705": "SLOVENIAN TOLAR SIT", - "706": "SOMALI SHILLING SOS", - "710": "RAND ZAR", - "716": "ZIMBABWE DOLLAR ZWD", - "720": "YEMENI DINAR YDD", - "728": "SOUTH SUDANESE POUND SSP", - "736": "SUDANESE POUND SDP", - "737": "SUDAN AIRLINES SDA", - "740": "SURINAM GUILDER SRG", - "748": "LILANGENI SZL", - "752": "SWEDISH KRONA SEK", - "756": "SWISS FRANC CHF", - "760": "SYRIAN POUND SYP", - "762": "TAJIK RUBLE TJR", - "764": "BAHT THB", - "776": "PA'ANGA TOP", - "780": "TRINIDAD Y TOBAGO DO TTD", - "784": "UAE DIRHAM AED", - "788": "TUNISIAN DINAR TND", - "792": "TURKISH LIRA TRL", - "793": "PSEUDO TURKISH LIRA PTL", - "795": "MANAT TMM", - "800": "UGANDA SHILLING UGS", - "804": "KARBOVANET UAK", - "807": "MACEDONIAN DENAR MKD", - "810": "RUSSIAN ROUBLE RUR", - "818": "EGYPTIAN POUND EGP", - "826": "POUND STERLING GBP", - "834": "TANZANIAN SHILLING TZS", - "840": "DOLAR U.S.A. USD", - "858": "URUGUAYAN PESO UYP", - "860": "UZBEKISTAN SUM UZS", - "862": "BOLIVAR VEB", - "882": "TALA WST", - "886": "YEMINI RIAL YER", - "890": "NEW YUGOSLAVIAN DOLL YUD", - "891": "NEW DINAR YUG", - "894": "KWACHA ZMK", - "901": "NEW TAIWAN DOLLAR TWD", - "934": "NEW MANAT TMT", - "936": "GHANA CEDI GHS", - "941": "DINAR SERBIO RSD", - "943": "MOZAMBIQUE METICAL MZN", - "944": "AZERBAIJANIAN MANAT AZN", - "946": "NEW LEU RON", - "949": "TURKISH LIRA TRY", - "950": "CFA FRANC BEAC XAF", - "951": "EAST CARIBBEAN DOLLA XCD", - "952": "CFA FRANC BCEAO XOF", - "953": "CFP FRANC XPF", - "954": "E.C.U. EUROPEAN CUR XEU", - "967": "KWACHA ZMW", - "968": "SURINAME DOLLAR SRD", - "969": "ARIARY MGA", - "971": "AFGHANISTAN AFGHANI AFN", - "972": "TAJIKISTAN SOMONI TJS", - "973": "KWANZA ANGOLA AOA", - "974": "BELARUSSIAN RUBLE BYR", - "975": "NEW LEV BGN", - "976": "FRANCO DEL CONGO CDF", - "977": "BOSNIAN MARKA BAM", - "978": "EURO EUR", - "980": "HRYVNIA UAH", - "981": "GEORGIAN LARI GEL", - "985": "NEW POLISH ZLOTY PLN", - "986": "BRAZILIAN REAL BRL", - "991": "RAND FINANCIER ZAL", + '8': 'LEK ALL', + '12': 'ALGERIAN DINAR DZD', + '24': 'ANGOLA KWANZA AOK', + '30': 'PROBANDO DESA MON', + '31': 'AZERBAIJANIAN MANAT AZM', + '32': 'ARGENTINE AUSTRAL ARP', + '36': 'AUSTRALIAN DOLLAR AUD', + '44': 'BAHAMIAN DOLLAR BSD', + '48': 'BAHRAINI DINAR BHD', + '50': 'TAKA BDT', + '51': 'ARMENIAN DRAM AMD', + '52': 'BARBADOS DOLLAR BBD', + '60': 'BERMUDAN DOLLAR BMD', + '64': 'NGULTRUM BTN', + '68': 'BOLIVIAN PESO BOP', + '70': 'DINAR BAD', + '72': 'PULA BWP', + '76': 'CRUZEIRO BRC', + '84': 'BELIZE DOLLAR 084', + '90': 'SOLOMON ISLANDS DOLL SBD', + '96': 'BRUNEI DOLLAR BND', + '100': 'LEV BGL', + '104': 'KYAT BUK', + '108': 'BURUNDI FRANC BIF', + '112': 'BELARUSSIAN RUBLE BYB', + '116': 'RIEL KHR', + '124': 'CANADIAN DOLLAR CAD', + '132': 'CAPE VERDE ESCUDO CVE', + '136': 'CAYMAN ISLANDS DOLLA KYD', + '144': 'SRI LANKA RUPEE LKR', + '152': 'CHILEAN PESO CLP', + '156': 'YUAN RENMINBI CNY', + '157': 'CHINESE RENMIMBI CNH', + '158': 'CHINESE RENMINBI CNX', + '170': 'COLOMBIAN PESO COP', + '174': 'COMOROS FRANC KMF', + '180': 'ZAIRE ZRZ', + '188': 'COSTA RICA COLON CRC', + '191': 'CROATIAN KUNA HRK', + '192': 'CUBAN PESO CUP', + '196': 'CYPRUS POUND CYP', + '200': 'KORUNA CSK', + '203': 'CZECH KORUNA CZK', + '208': 'DANISH KRONE DKK', + '214': 'DOMINICAN PESO DOP', + '218': 'SUCRE ECS', + '222': 'EL SALVADOR COLON SVC', + '226': 'EKWELE GQE', + '230': 'ETHIOPIAN BIRR ETB', + '232': 'ERITREAN NAKTAN ERN', + '233': 'ESTONIAN KROON EEK', + '238': 'FALKLAND ISLANDS FKP', + '242': 'FIJI DOLLAR FJD', + '262': 'DJIBOUTI FRANC DJF', + '268': 'GEORGIAN LARI GEL', + '270': 'DALASI GMD', + '278': 'MARK DER DDR DDM', + '288': 'GHANA CEDI GHC', + '292': 'GIBRALTAR POUND GIP', + '320': 'QUETZAL GTQ', + '324': 'SYLI GNS', + '328': 'GUYANA DOLLAR GYD', + '332': 'GOURDE HTG', + '340': 'LEMPIRA HNL', + '344': 'HONG KONG DOLLAR HKD', + '348': 'FORINT HUF', + '352': 'ICELAND KRONA ISK', + '356': 'INDIAN RUPEE INR', + '360': 'RUPIAH IDR', + '364': 'IRANIAL RIAL IRR', + '365': 'IRANIAN AIRLINE RATE IRA', + '368': 'IRAQI DINAR IQD', + '376': 'ISRAEL SHEKEL ILS', + '388': 'JAMAICAN DOLLAR JMD', + '392': 'YEN JPY', + '398': 'TENGE KZT', + '400': 'JORDANIAN DINAR JOD', + '404': 'KENYAN SHILLING KES', + '408': 'NORTH KOREAN WON KPW', + '410': 'KOREAN WON KRW', + '414': 'KUWAITI DINAR KWD', + '417': 'KYRGYZSTAN SON KGS', + '418': 'KIP LAK', + '422': 'LEBANESE POUND LBP', + '426': 'LESOTHO LOTI LSM', + '428': 'LATVIAN LAT LVL', + '430': 'LIBERIAN DOLLAR LRD', + '434': 'LIBYAN DINAR LYD', + '440': 'LITHUANIAN LITAS LTL', + '446': 'PATACA MOP', + '450': 'MALAGASY FRANC MGF', + '454': 'MALAWI KWACHA MWK', + '458': 'MALASYAN RINGGIT MYR', + '462': 'MALDIVE RUPEE MVR', + '466': 'MALI MLF', + '470': 'MALTESE LIRA MTL', + '478': 'OUGUIYA MRO', + '480': 'MAURITIUS RUPEE MUR', + '484': 'MEXICAN PESO MXP', + '496': 'TUGRIK MNT', + '498': 'MOLDOVIAN LEU MDL', + '504': 'MORROCAN DIRHAM MAD', + '508': 'METICAL MZM', + '512': 'RIAL OMANI OMR', + '516': 'NAMIBIAN DOLLAR NAD', + '524': 'NEPALESE RUPEE NPR', + '532': 'NETHERLANDS ANTILLIA ANG', + '533': 'ARUBA AWG', + '536': 'YUGOSLAVIAN NEW DIAN NTZ', + '548': 'VANUATU VATU VUV', + '554': 'NEW ZEALAND DOLLAR NZD', + '556': 'NAIRA 566', + '558': 'CORDOBA NIC', + '566': 'NAIRA NGN', + '578': 'NORWEGIAN KRONE NOK', + '582': 'PACIFIC ISLAND PCI', + '586': 'PAKISTAN RUPEE PKR', + '590': 'BALBOA PAB', + '598': 'KINA PGK', + '600': 'GUARANI PYG', + '604': 'PERU INTI PEI', + '608': 'PHILIPPINE PESO PHP', + '616': 'ZLOTY PLZ', + '624': 'GUINEA', + '626': 'TIMOR ESCUDO TPE', + '634': 'QATARI RIAL QAR', + '642': 'LEU ROL', + '643': 'RUSSIAN ROUBLE RUB', + '646': 'RWANDA FRANC RWF', + '654': 'ST.HELENA POUND SHP', + '678': 'DOBRA STD', + '682': 'SAUDI RIYAL SAR', + '690': 'SEYCHELLES RUPEE SCR', + '694': 'LEONE SLL', + '702': 'SINGAPORE DOLLAR SGD', + '703': 'SLOVAK KORUNA SKK', + '704': 'DONG VND', + '705': 'SLOVENIAN TOLAR SIT', + '706': 'SOMALI SHILLING SOS', + '710': 'RAND ZAR', + '716': 'ZIMBABWE DOLLAR ZWD', + '720': 'YEMENI DINAR YDD', + '728': 'SOUTH SUDANESE POUND SSP', + '736': 'SUDANESE POUND SDP', + '737': 'SUDAN AIRLINES SDA', + '740': 'SURINAM GUILDER SRG', + '748': 'LILANGENI SZL', + '752': 'SWEDISH KRONA SEK', + '756': 'SWISS FRANC CHF', + '760': 'SYRIAN POUND SYP', + '762': 'TAJIK RUBLE TJR', + '764': 'BAHT THB', + '776': "PA'ANGA TOP", + '780': 'TRINIDAD Y TOBAGO DO TTD', + '784': 'UAE DIRHAM AED', + '788': 'TUNISIAN DINAR TND', + '792': 'TURKISH LIRA TRL', + '793': 'PSEUDO TURKISH LIRA PTL', + '795': 'MANAT TMM', + '800': 'UGANDA SHILLING UGS', + '804': 'KARBOVANET UAK', + '807': 'MACEDONIAN DENAR MKD', + '810': 'RUSSIAN ROUBLE RUR', + '818': 'EGYPTIAN POUND EGP', + '826': 'POUND STERLING GBP', + '834': 'TANZANIAN SHILLING TZS', + '840': 'DOLAR U.S.A. USD', + '858': 'URUGUAYAN PESO UYP', + '860': 'UZBEKISTAN SUM UZS', + '862': 'BOLIVAR VEB', + '882': 'TALA WST', + '886': 'YEMINI RIAL YER', + '890': 'NEW YUGOSLAVIAN DOLL YUD', + '891': 'NEW DINAR YUG', + '894': 'KWACHA ZMK', + '901': 'NEW TAIWAN DOLLAR TWD', + '934': 'NEW MANAT TMT', + '936': 'GHANA CEDI GHS', + '941': 'DINAR SERBIO RSD', + '943': 'MOZAMBIQUE METICAL MZN', + '944': 'AZERBAIJANIAN MANAT AZN', + '946': 'NEW LEU RON', + '949': 'TURKISH LIRA TRY', + '950': 'CFA FRANC BEAC XAF', + '951': 'EAST CARIBBEAN DOLLA XCD', + '952': 'CFA FRANC BCEAO XOF', + '953': 'CFP FRANC XPF', + '954': 'E.C.U. EUROPEAN CUR XEU', + '967': 'KWACHA ZMW', + '968': 'SURINAME DOLLAR SRD', + '969': 'ARIARY MGA', + '971': 'AFGHANISTAN AFGHANI AFN', + '972': 'TAJIKISTAN SOMONI TJS', + '973': 'KWANZA ANGOLA AOA', + '974': 'BELARUSSIAN RUBLE BYR', + '975': 'NEW LEV BGN', + '976': 'FRANCO DEL CONGO CDF', + '977': 'BOSNIAN MARKA BAM', + '978': 'EURO EUR', + '980': 'HRYVNIA UAH', + '981': 'GEORGIAN LARI GEL', + '985': 'NEW POLISH ZLOTY PLN', + '986': 'BRAZILIAN REAL BRL', + '991': 'RAND FINANCIER ZAL', } ERROR_CODES = { - "10000": _("Error no tipificado"), - "0900": _("Transacción autorizada para devoluciones y confirmaciones"), - "0400": _("Transacción autorizada para anulaciones"), - "0101": _("Tarjeta caducada"), - "0102": _("Tarjeta en excepción transitoria o bajo sospecha de fraude"), - "0106": _("Intentos de PIN excedidos"), - "0125": _("Tarjeta no efectiva"), - "0129": _("Código de seguridad (CVV2/CVC2) incorrecto"), - "172": _("Denegada, no repetir."), - "173": _("Denegada, no repetir sin actualizar datos de tarjeta."), - "174": _("Denegada, no repetir antes de 72 horas."), - "0180": _("Tarjeta ajena al servicio"), - "0184": _("Error en la autenticación del titular"), - "0190": _("Denegación del emisor sin especificar motivo"), - "0191": _("Fecha de caducidad errónea"), - "0195": _("Requiere autenticación SCA"), - "0202": _( - "Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta" - ), - "0904": _("Comercio no registrado en FUC"), - "0909": _("Error de sistema"), - "0913": _("Pedido repetido"), - "0944": _("Sesión Incorrecta"), - "0950": _("Operación de devolución no permitida"), - "9912": _("Emisor no disponible"), - "0912": _("Emisor no disponible"), - "9064": _("Número de posiciones de la tarjeta incorrecto"), - "9078": _("Tipo de operación no permitida para esa tarjeta"), - "9093": _("Tarjeta no existente"), - "9094": _("Rechazo servidores internacionales"), - "9104": _("Comercio con titular seguro y titular sin clave de compra segura"), - "9218": _("El comercio no permite op. seguras por entrada /operaciones"), - "9253": _("Tarjeta no cumple el check-digit"), - "9256": _("El comercio no puede realizar preautorizaciones"), - "9257": _("Esta tarjeta no permite operativa de preautorizaciones"), - "9261": _( - "Operación detenida por superar el control de restricciones en la entrada al SIS" - ), - "9915": _("A petición del usuario se ha cancelado el pago"), - "9997": _("Se está procesando otra transacción en SIS con la misma tarjeta"), - "9998": _("Operación en proceso de solicitud de datos de tarjeta"), - "9999": _("Operación que ha sido redirigida al emisor a autenticar"), + '10000': _('Error no tipificado'), + '0900': _('Transacción autorizada para devoluciones y confirmaciones'), + '0400': _('Transacción autorizada para anulaciones'), + '0101': _('Tarjeta caducada'), + '0102': _('Tarjeta en excepción transitoria o bajo sospecha de fraude'), + '0106': _('Intentos de PIN excedidos'), + '0125': _('Tarjeta no efectiva'), + '0129': _('Código de seguridad (CVV2/CVC2) incorrecto'), + '172': _('Denegada, no repetir.'), + '173': _('Denegada, no repetir sin actualizar datos de tarjeta.'), + '174': _('Denegada, no repetir antes de 72 horas.'), + '0180': _('Tarjeta ajena al servicio'), + '0184': _('Error en la autenticación del titular'), + '0190': _('Denegación del emisor sin especificar motivo'), + '0191': _('Fecha de caducidad errónea'), + '0195': _('Requiere autenticación SCA'), + '0202': _('Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta'), + '0904': _('Comercio no registrado en FUC'), + '0909': _('Error de sistema'), + '0913': _('Pedido repetido'), + '0944': _('Sesión Incorrecta'), + '0950': _('Operación de devolución no permitida'), + '9912': _('Emisor no disponible'), + '0912': _('Emisor no disponible'), + '9064': _('Número de posiciones de la tarjeta incorrecto'), + '9078': _('Tipo de operación no permitida para esa tarjeta'), + '9093': _('Tarjeta no existente'), + '9094': _('Rechazo servidores internacionales'), + '9104': _('Comercio con titular seguro y titular sin clave de compra segura'), + '9218': _('El comercio no permite op. seguras por entrada /operaciones'), + '9253': _('Tarjeta no cumple el check-digit'), + '9256': _('El comercio no puede realizar preautorizaciones'), + '9257': _('Esta tarjeta no permite operativa de preautorizaciones'), + '9261': _('Operación detenida por superar el control de restricciones en la entrada al SIS'), + '9915': _('A petición del usuario se ha cancelado el pago'), + '9997': _('Se está procesando otra transacción en SIS con la misma tarjeta'), + '9998': _('Operación en proceso de solicitud de datos de tarjeta'), + '9999': _('Operación que ha sido redirigida al emisor a autenticar'), } diff --git a/shop/tests/mixins.py b/shop/tests/mixins.py index d5f1cf2..352e3ba 100644 --- a/shop/tests/mixins.py +++ b/shop/tests/mixins.py @@ -7,28 +7,9 @@ from shop.models import Product, ProductPrice, Tax class CreateProductsMixin: def create_product( - self, - sku="000001", - name="Producto 1", - description="Descripción", - price=Decimal("10.00"), - is_shipping=False, + self, sku='000001', name='Producto 1', description='Descripción', price=Decimal('10.00'), is_shipping=False ) -> Product: - tax, created = Tax.objects.get_or_create( - code="IVA", - value=21, - ) - product = Product.objects.create( - sku=sku, - name=name, - description=description, - is_shipping_method=is_shipping, - ) - ProductPrice.objects.create( - price=price, - product=product, - date=now(), - tax=tax, - current=True, - ) + tax, created = Tax.objects.get_or_create(code='IVA', value=21) + product = Product.objects.create(sku=sku, name=name, description=description, is_shipping_method=is_shipping) + ProductPrice.objects.create(price=price, product=product, date=now(), tax=tax, current=True) return product diff --git a/shop/tests/test_images.py b/shop/tests/test_images.py index 29a2699..4b410ff 100644 --- a/shop/tests/test_images.py +++ b/shop/tests/test_images.py @@ -13,11 +13,11 @@ class TestProductImages(TestCase, CreateProductsMixin): self.product = self.create_product() def create_image_file(self, size=(768, 768)): - image = Image.new("RGB", size, "#ACACAC") + image = Image.new('RGB', size, '#ACACAC') buffer = BytesIO() - image.save(fp=buffer, format="WEBP") - file = ContentFile(buffer.getvalue(), name=f"{self.product.slug}.webp") + image.save(fp=buffer, format='WEBP') + file = ContentFile(buffer.getvalue(), name=f'{self.product.slug}.webp') return file def test_product_image_creation(self): diff --git a/shop/tests/test_redsys.py b/shop/tests/test_redsys.py index bc6eb30..3ab170c 100644 --- a/shop/tests/test_redsys.py +++ b/shop/tests/test_redsys.py @@ -22,58 +22,51 @@ User = get_user_model() def redsys_response_ok(*args, **kwargs): response = { - "Ds_SignatureVersion": "HMAC_SHA256_V1", - "Ds_MerchantParameters": "eyJEc19BbW91bnQiOiIxNDUiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTQ0NjA2ODU4MSIsIkRzX01lcmNoYW50Q29kZSI6Ijk5OTAwODg4MSIsIkRzX1Rlcm1pbmFsIjoiMSIsIkRzX1Jlc3BvbnNlIjoiMDAwMCIsIkRzX0F1dGhvcmlzYXRpb25Db2RlIjoiNTAxNjAyIiwiRHNfVHJhbnNhY3Rpb25UeXBlIjoiMCIsIkRzX1NlY3VyZVBheW1lbnQiOiIwIiwiRHNfTGFuZ3VhZ2UiOiIxIiwiRHNfQ2FyZE51bWJlciI6IjQ1NDg4MSoqKioqKioqMDQiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSJ9", - "Ds_Signature": "QVxoXwwp919v7XYjyBjhr1VXozESRosHPb3PDW-rcME=", + 'Ds_SignatureVersion': 'HMAC_SHA256_V1', + 'Ds_MerchantParameters': 'eyJEc19BbW91bnQiOiIxNDUiLCJEc19DdXJyZW5jeSI6Ijk3OCIsIkRzX09yZGVyIjoiMTQ0NjA2ODU4MSIsIkRzX01lcmNoYW50Q29kZSI6Ijk5OTAwODg4MSIsIkRzX1Rlcm1pbmFsIjoiMSIsIkRzX1Jlc3BvbnNlIjoiMDAwMCIsIkRzX0F1dGhvcmlzYXRpb25Db2RlIjoiNTAxNjAyIiwiRHNfVHJhbnNhY3Rpb25UeXBlIjoiMCIsIkRzX1NlY3VyZVBheW1lbnQiOiIwIiwiRHNfTGFuZ3VhZ2UiOiIxIiwiRHNfQ2FyZE51bWJlciI6IjQ1NDg4MSoqKioqKioqMDQiLCJEc19NZXJjaGFudERhdGEiOiIiLCJEc19DYXJkX0NvdW50cnkiOiI3MjQiLCJEc19DYXJkX0JyYW5kIjoiMSJ9', + 'Ds_Signature': 'QVxoXwwp919v7XYjyBjhr1VXozESRosHPb3PDW-rcME=', } return Response(response, status_code=200) def redsys_response_error(*args, **kwargs): - response = {"errorCode": "SIS00001"} + response = {'errorCode': 'SIS00001'} return Response(response, status_code=200) class TestRedsysTPV(APITestCase, CreateProductsMixin): def setUp(self): - self.tax = Tax.objects.create( - code="IVA", - value=21, - ) + self.tax = Tax.objects.create(code='IVA', value=21) self.customer = get_user_model().objects.create_user( - username="11111111H", - first_name="Darth", - last_name="Maul", - email="darth@maul.com", - password="dathomir", + username='11111111H', first_name='Darth', last_name='Maul', email='darth@maul.com', password='dathomir' ) self.customer_shipping_address = CustomerAddress.objects.create( user=self.customer, - address="Dathomir", - address_town="Dathomir", - address_zip="00001", - address_state="Dathomir", - address_phone="900000000", + address='Dathomir', + address_town='Dathomir', + address_zip='00001', + address_state='Dathomir', + address_phone='900000000', address_type=CustomerAddress.Types.SHIPPING, ) self.customer_billing_address = CustomerAddress.objects.create( user=self.customer, - address="Dathomir", - address_town="Dathomir", - address_zip="00001", - address_state="Dathomir", - address_phone="900000000", + address='Dathomir', + address_town='Dathomir', + address_zip='00001', + address_state='Dathomir', + address_phone='900000000', address_type=CustomerAddress.Types.BILLING, ) self.product = self.create_product() self.order = self.create_order() self.order.calculate_total_from_lines() self.settings = ShopSettings.load() - self.settings.merchant_code = "999008881" - self.settings.shared_secret = "sq7HjrUOBfKmC576ILgskD5srU870gJ7" # Debug secret + self.settings.merchant_code = '999008881' + self.settings.shared_secret = 'sq7HjrUOBfKmC576ILgskD5srU870gJ7' # Debug secret self.settings.save() def create_order(self): @@ -86,66 +79,53 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin): billing_country=self.customer_billing_address.address_country, ) - l1 = create_order_line_for_product( - self.product, - quantity=Decimal("1.0"), - order=order, - ) + create_order_line_for_product(self.product, quantity=Decimal('1.0'), order=order) return order def test_redsys_client(self): - amount_to_pay = Decimal("12.10") + amount_to_pay = Decimal('12.10') client = RedsysClient() - merchant_parameters = client._get_merchant_parameters_for_order( - self.order, - ) + merchant_parameters = client._get_merchant_parameters_for_order(self.order) - assert merchant_parameters.get("DS_MERCHANT_ORDER") == self.order.code - assert merchant_parameters.get("DS_MERCHANT_AMOUNT") == str( - int(amount_to_pay * 100) - ) - assert merchant_parameters.get("DS_MERCHANT_TERMINAL") == self.settings.terminal - assert ( - merchant_parameters.get("DS_MERCHANT_MERCHANTCODE") - == self.settings.merchant_code - ) + assert merchant_parameters.get('DS_MERCHANT_ORDER') == self.order.code + assert merchant_parameters.get('DS_MERCHANT_AMOUNT') == str(int(amount_to_pay * 100)) + assert merchant_parameters.get('DS_MERCHANT_TERMINAL') == self.settings.terminal + assert merchant_parameters.get('DS_MERCHANT_MERCHANTCODE') == self.settings.merchant_code client.get_body_for_order(self.order) def test_redsys_webhook(self): redsys_response_data = { - "Ds_MerchantCode": "999008881", - "Ds_Terminal": "001", - "Ds_Order": self.order.code, - "Ds_Amount": str(self.order.total * 100), - "Ds_Currency": "978", - "Ds_Date": "01/01/2024", - "Ds_Hour": "00:00", - "Ds_SecurePayment": "1", - "Ds_Card_Number": "454881******1156", - "Ds_Card_Country": "724", - "Ds_Response": "0000", - "Ds_MerchantData": "", - "Ds_TransactionType": "0", - "Ds_ConsumerLanguage": "1", - "Ds_AuthorisationCode": "182670", - "Ds_Card_Brand": "1", - "Ds_ProcessedPayMethod": "80", - "Ds_ECI": "05", - "Ds_Response_Description": "OPERACION AUTORIZADA", + 'Ds_MerchantCode': '999008881', + 'Ds_Terminal': '001', + 'Ds_Order': self.order.code, + 'Ds_Amount': str(self.order.total * 100), + 'Ds_Currency': '978', + 'Ds_Date': '01/01/2024', + 'Ds_Hour': '00:00', + 'Ds_SecurePayment': '1', + 'Ds_Card_Number': '454881******1156', + 'Ds_Card_Country': '724', + 'Ds_Response': '0000', + 'Ds_MerchantData': '', + 'Ds_TransactionType': '0', + 'Ds_ConsumerLanguage': '1', + 'Ds_AuthorisationCode': '182670', + 'Ds_Card_Brand': '1', + 'Ds_ProcessedPayMethod': '80', + 'Ds_ECI': '05', + 'Ds_Response_Description': 'OPERACION AUTORIZADA', } redsys_response_data_str = json.dumps(redsys_response_data) - b64_merchant_params = base64.b64encode( - redsys_response_data_str.encode() - ).decode() + b64_merchant_params = base64.b64encode(redsys_response_data_str.encode()).decode() response = self.client.post( - reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), + reverse('shop:webhook', kwargs={'uuid': self.order.uuid}), data={ - "Ds_MerchantParameters": b64_merchant_params, - "Ds_Signature": self.settings.shared_secret, - "Ds_SignatureVersion": "HMAC_SHA256_V1", + 'Ds_MerchantParameters': b64_merchant_params, + 'Ds_Signature': self.settings.shared_secret, + 'Ds_SignatureVersion': 'HMAC_SHA256_V1', }, ) self.order.refresh_from_db() @@ -154,38 +134,36 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin): def test_redsys_webhook_payment_error(self): redsys_response_data = { - "Ds_MerchantCode": "999008881", - "Ds_Terminal": "001", - "Ds_Order": self.order.code, - "Ds_Amount": "1000", - "Ds_Currency": "978", - "Ds_Date": "01/01/2024", - "Ds_Hour": "00:00", - "Ds_SecurePayment": "1", - "Ds_Card_Number": "454881******1156", - "Ds_Card_Country": "724", - "Ds_Response": "0184", - "Ds_MerchantData": "", - "Ds_TransactionType": "0", - "Ds_ConsumerLanguage": "1", - "Ds_AuthorisationCode": "182670", - "Ds_Card_Brand": "1", - "Ds_ProcessedPayMethod": "80", - "Ds_ECI": "05", - "Ds_Response_Description": "ERROR", + 'Ds_MerchantCode': '999008881', + 'Ds_Terminal': '001', + 'Ds_Order': self.order.code, + 'Ds_Amount': '1000', + 'Ds_Currency': '978', + 'Ds_Date': '01/01/2024', + 'Ds_Hour': '00:00', + 'Ds_SecurePayment': '1', + 'Ds_Card_Number': '454881******1156', + 'Ds_Card_Country': '724', + 'Ds_Response': '0184', + 'Ds_MerchantData': '', + 'Ds_TransactionType': '0', + 'Ds_ConsumerLanguage': '1', + 'Ds_AuthorisationCode': '182670', + 'Ds_Card_Brand': '1', + 'Ds_ProcessedPayMethod': '80', + 'Ds_ECI': '05', + 'Ds_Response_Description': 'ERROR', } redsys_response_data_str = json.dumps(redsys_response_data) - b64_merchant_params = base64.b64encode( - redsys_response_data_str.encode() - ).decode() + b64_merchant_params = base64.b64encode(redsys_response_data_str.encode()).decode() response = self.client.post( - reverse("shop:webhook", kwargs={"uuid": self.order.uuid}), + reverse('shop:webhook', kwargs={'uuid': self.order.uuid}), data={ - "Ds_MerchantParameters": b64_merchant_params, - "Ds_Signature": self.settings.shared_secret, - "Ds_SignatureVersion": "HMAC_SHA256_V1", + 'Ds_MerchantParameters': b64_merchant_params, + 'Ds_Signature': self.settings.shared_secret, + 'Ds_SignatureVersion': 'HMAC_SHA256_V1', }, ) @@ -196,37 +174,27 @@ class TestRedsysTPV(APITestCase, CreateProductsMixin): def test_expiry_date(self): now = timezone.now() - previous_year = str(now.year - 1).rjust(2, "0") - current_month = str(now.month).rjust(2, "0") + previous_year = str(now.year - 1).rjust(2, '0') + current_month = str(now.month).rjust(2, '0') current_year = str(now.year)[-2:] - assert not validate_expiry_date("042024") - assert not validate_expiry_date(f"{previous_year}{current_month}") + assert not validate_expiry_date('042024') + assert not validate_expiry_date(f'{previous_year}{current_month}') - assert validate_expiry_date(f"{current_year}{current_month}") + assert validate_expiry_date(f'{current_year}{current_month}') - @patch("requests.post", redsys_response_ok) + @patch('requests.post', redsys_response_ok) def test_redsys_rest_client(self): client = RedsysClient() - params = client.pay_order_rest( - self.order, - pan="4548810000000003", - expiry_date="122049", - cvv2="285", - ) + params = client.pay_order_rest(self.order, pan='4548810000000003', expiry_date='122049', cvv2='285') - assert params.get("Ds_Order") == "1446068581" - assert params.get("Ds_MerchantCode") == self.settings.merchant_code + assert params.get('Ds_Order') == '1446068581' + assert params.get('Ds_MerchantCode') == self.settings.merchant_code - @patch("requests.post", redsys_response_error) + @patch('requests.post', redsys_response_error) def test_redsys_rest_client_error(self): client = RedsysClient() with pytest.raises(RedsysPaymentException): - client.pay_order_rest( - self.order, - pan="4548810000000003", - expiry_date="122049", - cvv2="285", - ) + client.pay_order_rest(self.order, pan='4548810000000003', expiry_date='122049', cvv2='285') diff --git a/shop/urls.py b/shop/urls.py index 4b8a6f8..558cce9 100644 --- a/shop/urls.py +++ b/shop/urls.py @@ -2,9 +2,7 @@ from django.urls import path from shop.views import webhook -app_name = "shop" +app_name = 'shop' -urlpatterns = [ - path("order//webhook/", webhook, name="webhook"), -] +urlpatterns = [path('order//webhook/', webhook, name='webhook')] diff --git a/shop/utils.py b/shop/utils.py index b7c2bee..f1d27b7 100644 --- a/shop/utils.py +++ b/shop/utils.py @@ -12,17 +12,7 @@ from django.utils import timezone from django.utils.text import gettext_lazy as _ from shop.exceptions import RedsysPaymentException, RedsysValidationException -from shop.models import ( - Cart, - CartItem, - Order, - OrderLine, - Payment, - Product, - ProductBatch, - ProductPrice, - ShippingMethod, -) +from shop.models import Cart, CartItem, Order, OrderLine, Payment, Product, ProductBatch, ProductPrice, ShippingMethod from shop.settings import ERROR_CODES from shop.signals import clear_cart @@ -32,7 +22,7 @@ User = get_user_model() def create_order_line_for_product(product: Product, quantity: Decimal, order: Order): price = product.prices.last() base_total = round(price.price * quantity, 2) - tax_value = price.tax.value / Decimal("100") + tax_value = price.tax.value / Decimal('100') taxes = round(base_total * tax_value, 2) return OrderLine.objects.create( @@ -54,11 +44,11 @@ def create_order( billing_state: str, billing_country: str, billing_zip: str, - shipping_address: str = "", - shipping_city: str = "", - shipping_state: str = "", - shipping_country: str = "", - shipping_zip: str = "", + shipping_address: str = '', + shipping_city: str = '', + shipping_state: str = '', + shipping_country: str = '', + shipping_zip: str = '', ) -> Order: order = Order.objects.create( user=customer, @@ -87,29 +77,29 @@ def delete_product_batch(batch: ProductBatch): def create_order_from_cart( cart: Cart, shipping_method: ShippingMethod, - billing_address_full_name="", - billing_address_address="", - billing_address_town="", - billing_address_state="", - billing_address_country="", - billing_address_zip="", - shipping_address_full_name="", - shipping_address_address="", - shipping_address_town="", - shipping_address_state="", - shipping_address_country="", - shipping_address_zip="", - shipping_address_phone="", - email="", + billing_address_full_name='', + billing_address_address='', + billing_address_town='', + billing_address_state='', + billing_address_country='', + billing_address_zip='', + shipping_address_full_name='', + shipping_address_address='', + shipping_address_town='', + shipping_address_state='', + shipping_address_country='', + shipping_address_zip='', + shipping_address_phone='', + email='', ): order = Order.objects.create( - billing_address=f"{billing_address_full_name} {billing_address_address}", + billing_address=f'{billing_address_full_name} {billing_address_address}', billing_city=billing_address_town, billing_state=billing_address_state, billing_country=billing_address_country, billing_zip=billing_address_zip, contact_phone=shipping_address_phone, - shipping_address=f"{shipping_address_address} {shipping_address_full_name}", + shipping_address=f'{shipping_address_address} {shipping_address_full_name}', shipping_city=shipping_address_town, shipping_state=shipping_address_state, shipping_country=shipping_address_country, @@ -174,9 +164,7 @@ def compute_signature(salt, payload, key): """ b64_key = base64.b64decode(key) - des3 = pyDes.triple_des( - b64_key, mode=pyDes.CBC, IV="\0" * 8, pad="\0", padmode=pyDes.PAD_NORMAL - ) + des3 = pyDes.triple_des(b64_key, mode=pyDes.CBC, IV='\0' * 8, pad='\0', padmode=pyDes.PAD_NORMAL) pepper = des3.encrypt(str(salt)) payload_hash = hmac.new(pepper, payload.encode(), hashlib.sha256).digest() @@ -211,27 +199,25 @@ def validate_payment_for_order(request, order: Order) -> Decimal: """ data = request.POST - merchant_parameters = data.get("Ds_MerchantParameters") + merchant_parameters = data.get('Ds_MerchantParameters') if not merchant_parameters: - raise RedsysValidationException( - _("No se ha recibido ningún valor para Ds_MerchantParameters") - ) + raise RedsysValidationException(_('No se ha recibido ningún valor para Ds_MerchantParameters')) merchant_params = decode_b64_string(merchant_parameters) result = json.loads(merchant_params) - order_code = result.get("Ds_Order") + order_code = result.get('Ds_Order') assert order_code == order.code - status_code = result.get("Ds_Response") + status_code = result.get('Ds_Response') if int(status_code) > 100: - reason = ERROR_CODES.get(status_code, _("Error no tipificado")) + reason = ERROR_CODES.get(status_code, _('Error no tipificado')) - raise RedsysPaymentException(_(f"No se ha realizado el pago. Motivo: {reason}")) + raise RedsysPaymentException(_(f'No se ha realizado el pago. Motivo: {reason}')) - amount = Decimal(result.get("Ds_Amount")) / 100 + amount = Decimal(result.get('Ds_Amount')) / 100 return amount @@ -268,9 +254,7 @@ def validate_expiry_date(expiry_date: str): def update_order_payment_status(order: Order): if order.amount_paid >= order.total: order.status = Order.Statuses.STATUS_PAID - elif order.status == Order.Statuses.STATUS_PAID and order.amount_paid == Decimal( - "0.00" - ): + elif order.status == Order.Statuses.STATUS_PAID and order.amount_paid == Decimal('0.00'): order.status = Order.Statuses.STATUS_RETURNED else: order.status = Order.Statuses.STATUS_PENDING @@ -286,10 +270,7 @@ def delete_cart_items_from_order(order): def add_payment_to_order(order: Order, amount): with transaction.atomic(): payment = Payment.objects.create( - amount=amount, - order=order, - user=order.user, - method=Payment.MethodChoices.REDSYS, + amount=amount, order=order, user=order.user, method=Payment.MethodChoices.REDSYS ) order.amount_paid += payment.amount diff --git a/shop/views.py b/shop/views.py index fd518ef..157b590 100644 --- a/shop/views.py +++ b/shop/views.py @@ -15,7 +15,7 @@ def webhook(request, uuid): add_payment_to_order(order, amount_paid) return HttpResponse(status=200) - except Exception as e: + except Exception: order.status = Order.Statuses.STATUS_ERROR order.save() diff --git a/users/apps.py b/users/apps.py index 88f7b17..72b1401 100644 --- a/users/apps.py +++ b/users/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class UsersConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "users" + default_auto_field = 'django.db.models.BigAutoField' + name = 'users' diff --git a/users/forms/change_password.py b/users/forms/change_password.py index 10ca23b..0f8ddaa 100644 --- a/users/forms/change_password.py +++ b/users/forms/change_password.py @@ -8,9 +8,9 @@ User = get_user_model() class ChangePasswordForm(StylingMixin, PasswordChangeForm): - styled_fields = ("old_password", "new_password1", "new_password2") + styled_fields = ('old_password', 'new_password1', 'new_password2') placeholder_for_field = { - "old_password": _("********"), - "new_password1": _("********"), - "new_password2": _("********"), + 'old_password': _('********'), + 'new_password1': _('********'), + 'new_password2': _('********'), } diff --git a/users/forms/info.py b/users/forms/info.py index 7adcd18..2bbeb0a 100644 --- a/users/forms/info.py +++ b/users/forms/info.py @@ -8,15 +8,11 @@ User = get_user_model() class UserInfoForm(StylingMixin, forms.ModelForm): - styled_fields = ("email", "first_name", "last_name") - placeholder_for_field = { - "email": _("pablo@shoppy.com"), - "first_name": _("Pablo"), - "last_name": _("Moreno"), - } + styled_fields = ('email', 'first_name', 'last_name') + placeholder_for_field = {'email': _('pablo@shoppy.com'), 'first_name': _('Pablo'), 'last_name': _('Moreno')} def save(self, commit=True): - self.instance.username = self.cleaned_data.get("email") + self.instance.username = self.cleaned_data.get('email') return super().save(commit) def is_valid(self): @@ -24,8 +20,4 @@ class UserInfoForm(StylingMixin, forms.ModelForm): class Meta: model = User - fields = ( - "email", - "first_name", - "last_name", - ) + fields = ('email', 'first_name', 'last_name') diff --git a/users/forms/login.py b/users/forms/login.py index 29424ec..ae7c905 100644 --- a/users/forms/login.py +++ b/users/forms/login.py @@ -7,28 +7,16 @@ from web.mixins import StylingMixin class LoginForm(StylingMixin, AuthenticationForm): styled_fields = [] - placeholder_for_field = {"username": _("pablo@shoppy.com"), "password": "********"} + placeholder_for_field = {'username': _('pablo@shoppy.com'), 'password': '********'} - username = UsernameField( - widget=forms.EmailInput( - attrs={ - "autofocus": True, - "class": "input-text", - } - ) - ) + username = UsernameField(widget=forms.EmailInput(attrs={'autofocus': True, 'class': 'input-text'})) password = forms.CharField( - label=_("Contraseña"), + label=_('Contraseña'), strip=False, - widget=forms.PasswordInput( - attrs={ - "autocomplete": "current-password", - "class": "input-text", - } - ), + widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'class': 'input-text'}), ) remember_me = forms.BooleanField( - label=_("Mantener sesión iniciada"), + label=_('Mantener sesión iniciada'), required=False, - widget=forms.CheckboxInput(attrs={"class": "input-checkbox"}), + widget=forms.CheckboxInput(attrs={'class': 'input-checkbox'}), ) diff --git a/users/forms/register.py b/users/forms/register.py index 451c5ff..ee04725 100644 --- a/users/forms/register.py +++ b/users/forms/register.py @@ -9,13 +9,13 @@ from web.mixins import StylingMixin class RegisterForm(StylingMixin, BaseUserCreationForm, SetPasswordMixin): password1, password2 = SetPasswordMixin.create_password_fields() - styled_fields = ["email", "first_name", "last_name", "password1", "password2"] + styled_fields = ['email', 'first_name', 'last_name', 'password1', 'password2'] placeholder_for_field = { - "email": _("pablo@shoppy.com"), - "first_name": _("Pablo"), - "last_name": _("Moreno"), - "password1": "********", - "password2": "********", + 'email': _('pablo@shoppy.com'), + 'first_name': _('Pablo'), + 'last_name': _('Moreno'), + 'password1': '********', + 'password2': '********', } def __init__(self, *args, **kwargs): @@ -24,22 +24,14 @@ class RegisterForm(StylingMixin, BaseUserCreationForm, SetPasswordMixin): self.fields[field_name].required = True self.fields[field_name].widget.attrs.update( { - "class": "my-2 bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg " - "focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 " - "dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 " - "dark:focus:border-primary-500" + 'class': 'my-2 bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg ' + 'focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 ' + 'dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 ' + 'dark:focus:border-primary-500' } ) class Meta: model = User - fields = ( - "email", - "first_name", - "last_name", - ) - field_classes = { - "email": EmailField, - "first_name": forms.CharField, - "last_name": forms.CharField, - } + fields = ('email', 'first_name', 'last_name') + field_classes = {'email': EmailField, 'first_name': forms.CharField, 'last_name': forms.CharField} diff --git a/users/forms/reset_password.py b/users/forms/reset_password.py index ecbc734..323b367 100644 --- a/users/forms/reset_password.py +++ b/users/forms/reset_password.py @@ -9,42 +9,26 @@ from web.mixins import StylingMixin class PasswordResetForm(StylingMixin, BasePasswordResetForm): - styled_fields = [ - "email", - ] - placeholder_for_field = { - "email": _("pablo@shoppy.com"), - } + styled_fields = ['email'] + placeholder_for_field = {'email': _('pablo@shoppy.com')} class SetPasswordForm(StylingMixin, BaseSetPasswordForm): - styled_fields = [ - "new_password1", - "new_password2", - ] - placeholder_for_field = { - "new_password1": "********", - "new_password2": "********", - } + styled_fields = ['new_password1', 'new_password2'] + placeholder_for_field = {'new_password1': '********', 'new_password2': '********'} - token = forms.CharField( - required=True, - widget=forms.HiddenInput(), - validators=[], - ) + token = forms.CharField(required=True, widget=forms.HiddenInput(), validators=[]) def validate_token(self): - is_valid = default_token_generator.check_token( - self.user, self.data.get("token") - ) + is_valid = default_token_generator.check_token(self.user, self.data.get('token')) if not is_valid: - raise ValidationError(_("El token no es válido o ha expirado")) + raise ValidationError(_('El token no es válido o ha expirado')) def is_valid(self): try: self.validate_token() except ValidationError as e: - self.add_error("token", str(e)) + self.add_error('token', str(e)) return False return super().is_valid() diff --git a/users/tests/test_users_model.py b/users/tests/test_users_model.py index efe1f0e..e5223b8 100644 --- a/users/tests/test_users_model.py +++ b/users/tests/test_users_model.py @@ -7,25 +7,22 @@ User = get_user_model() class TestUsers(TestCase): - def test_create_user(self): - User.objects.create_user({"username": "user 1", "password": "password1"}) + User.objects.create_user({'username': 'user 1', 'password': 'password1'}) User.objects.all().count() == 1 def test_new_user_invalid_username(self): """Test creating user with no username raises error""" with pytest.raises(ValueError): - User.objects.create_user(None, "test123") + User.objects.create_user(None, 'test123') def test_create_new_superuser(self): """Test creating a new superuser""" # Creation with standard method - user = User.objects.create_superuser( - "testsuperuser@adminemail.com", "testadmin123" - ) + user = User.objects.create_superuser('testsuperuser@adminemail.com', 'testadmin123') assert user.is_superuser assert user.is_staff def test_create_group(self): - Group.objects.create(name="Group1") + Group.objects.create(name='Group1') assert Group.objects.count() == 1 diff --git a/users/urls.py b/users/urls.py index c22d6dd..3f93b35 100644 --- a/users/urls.py +++ b/users/urls.py @@ -1,4 +1,4 @@ -app_name = "users" +app_name = 'users' urlpatterns = [] diff --git a/web/apps.py b/web/apps.py index e3fe3ab..6a969af 100644 --- a/web/apps.py +++ b/web/apps.py @@ -2,8 +2,8 @@ from django.apps import AppConfig class WebConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "web" + default_auto_field = 'django.db.models.BigAutoField' + name = 'web' def ready(self): from .signals import rebuild_theme diff --git a/web/context_processors.py b/web/context_processors.py index 85afc1c..a368df1 100644 --- a/web/context_processors.py +++ b/web/context_processors.py @@ -4,12 +4,10 @@ from web.models import WebSettings def web_settings(request): settings = WebSettings.load() - navbar_categories = ProductCategory.objects.filter( - hidden=False, show_in_navbar=True - ) + navbar_categories = ProductCategory.objects.filter(hidden=False, show_in_navbar=True) return { - "web_title": settings.web_title, - "logo": settings.logo_32.url if settings.logo_32 else "", - "navbar_categories": navbar_categories, + 'web_title': settings.web_title, + 'logo': settings.logo_32.url if settings.logo_32 else '', + 'navbar_categories': navbar_categories, } diff --git a/web/forms.py b/web/forms.py index d5f4d5f..85b1fd0 100644 --- a/web/forms.py +++ b/web/forms.py @@ -42,12 +42,4 @@ class CreateOrderForm(forms.Form): class CustomerAddressForm(forms.ModelForm): class Meta: model = CustomerAddress - fields = ( - "vat_id", - "full_name", - "address", - "address_town", - "address_zip", - "address_state", - "address_phone", - ) + fields = ('vat_id', 'full_name', 'address', 'address_town', 'address_zip', 'address_state', 'address_phone') diff --git a/web/management/commands/build_tailwind_theme.py b/web/management/commands/build_tailwind_theme.py index fde8195..2af0bfe 100644 --- a/web/management/commands/build_tailwind_theme.py +++ b/web/management/commands/build_tailwind_theme.py @@ -26,7 +26,7 @@ class Command(BaseCommand): def handle(self, *args, **options): web_settings = WebSettings.load() - path = os.path.join(settings.BASE_DIR, "web", "static", "css", "main.css") + path = os.path.join(settings.BASE_DIR, 'web', 'static', 'css', 'main.css') colors = [] for color_name, data in web_settings.theme_colors.items(): @@ -34,23 +34,9 @@ class Command(BaseCommand): color = Color(color_name, index, value) colors.append(color) - result = str( - render_to_string( - "theme/main.css.template", - context={"colors": colors}, - ) - ) - with open(path, "w") as f: + result = str(render_to_string('theme/main.css.template', context={'colors': colors})) + with open(path, 'w') as f: f.write(result) - subprocess.run( - [ - "tailwindcss", - "-i", - "web/static/css/main.css", - "-o", - "web/static/css/styles.css", - "--minify", - ] - ) - call_command("collectstatic", "--no-input") + subprocess.run(['tailwindcss', '-i', 'web/static/css/main.css', '-o', 'web/static/css/styles.css', '--minify']) + call_command('collectstatic', '--no-input') diff --git a/web/migrations/0001_initial.py b/web/migrations/0001_initial.py index b14f983..6a8044a 100644 --- a/web/migrations/0001_initial.py +++ b/web/migrations/0001_initial.py @@ -4,171 +4,92 @@ from django.db import migrations, models class Migration(migrations.Migration): - initial = True dependencies = [] operations = [ migrations.CreateModel( - name="WebSettings", + name='WebSettings', fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('web_title', models.CharField(max_length=32, verbose_name='título de la web')), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + 'web_description', + models.CharField(blank=True, default='', max_length=128, verbose_name='descripción de la web'), ), + ('logo', models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo de la web')), ( - "web_title", - models.CharField(max_length=32, verbose_name="título de la web"), - ), - ( - "web_description", + 'business_name', models.CharField( blank=True, - default="", - max_length=128, - verbose_name="descripción de la web", - ), - ), - ( - "logo", - models.ImageField( - blank=True, - null=True, - upload_to="assets", - verbose_name="logo de la web", - ), - ), - ( - "business_name", - models.CharField( - blank=True, - help_text="nombre fiscal del comercio", + help_text='nombre fiscal del comercio', max_length=128, null=True, - verbose_name="nombre de negocio", + verbose_name='nombre de negocio', ), ), ( - "business_vat_id", - models.CharField( - blank=True, - max_length=16, - null=True, - verbose_name="CIF del negocio", - ), + 'business_vat_id', + models.CharField(blank=True, max_length=16, null=True, verbose_name='CIF del negocio'), ), ( - "business_brand", + 'business_brand', models.CharField( blank=True, - help_text="marca o nombre comercial que se va a mostrar en la web", + help_text='marca o nombre comercial que se va a mostrar en la web', max_length=64, null=True, - verbose_name="marca de negocio", + verbose_name='marca de negocio', ), ), ( - "business_address", + 'business_address', + models.CharField(blank=True, max_length=64, null=True, verbose_name='dirección de negocio'), + ), + ( + 'business_state', models.CharField( - blank=True, - max_length=64, - null=True, - verbose_name="dirección de negocio", + blank=True, max_length=32, null=True, verbose_name='unidad territorial del negocio' ), ), ( - "business_state", + 'business_zip', + models.CharField(blank=True, max_length=16, null=True, verbose_name='código postal del negocio'), + ), + ( + 'business_phone', models.CharField( - blank=True, - max_length=32, - null=True, - verbose_name="unidad territorial del negocio", + blank=True, max_length=16, null=True, verbose_name='número de teléfono de contacto' ), ), ( - "business_zip", - models.CharField( - blank=True, - max_length=16, - null=True, - verbose_name="código postal del negocio", - ), + 'business_email', + models.EmailField(blank=True, max_length=80, null=True, verbose_name='e-mail de contacto'), ), ( - "business_phone", - models.CharField( - blank=True, - max_length=16, - null=True, - verbose_name="número de teléfono de contacto", - ), - ), - ( - "business_email", + 'business_email_2', models.EmailField( - blank=True, - max_length=80, - null=True, - verbose_name="e-mail de contacto", + blank=True, max_length=80, null=True, verbose_name='e-mail secundario de contacto' ), ), ( - "business_email_2", - models.EmailField( - blank=True, - max_length=80, - null=True, - verbose_name="e-mail secundario de contacto", - ), + 'bg_color', + models.CharField(blank=True, default='', max_length=9, null=True, verbose_name='color de fondo'), ), ( - "bg_color", - models.CharField( - blank=True, - default="", - max_length=9, - null=True, - verbose_name="color de fondo", - ), + 'theme_color', + models.CharField(blank=True, default='', max_length=9, null=True, verbose_name='color de tema'), ), ( - "theme_color", - models.CharField( - blank=True, - default="", - max_length=9, - null=True, - verbose_name="color de tema", - ), + 'logo_240', + models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo 240x240'), ), ( - "logo_240", - models.ImageField( - blank=True, - null=True, - upload_to="assets", - verbose_name="logo 240x240", - ), - ), - ( - "logo_128", - models.ImageField( - blank=True, - null=True, - upload_to="assets", - verbose_name="logo 128x128", - ), + 'logo_128', + models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo 128x128'), ), ], - options={ - "verbose_name": "ajustes de la web", - "verbose_name_plural": "ajustes de la web", - }, - ), + options={'verbose_name': 'ajustes de la web', 'verbose_name_plural': 'ajustes de la web'}, + ) ] diff --git a/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py b/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py index 92d8afd..0c991a8 100644 --- a/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py +++ b/web/migrations/0002_remove_websettings_logo_240_websettings_logo_256.py @@ -4,21 +4,13 @@ from django.db import migrations, models class Migration(migrations.Migration): - - dependencies = [ - ("web", "0001_initial"), - ] + dependencies = [('web', '0001_initial')] operations = [ - migrations.RemoveField( - model_name="websettings", - name="logo_240", - ), + migrations.RemoveField(model_name='websettings', name='logo_240'), migrations.AddField( - model_name="websettings", - name="logo_256", - field=models.ImageField( - blank=True, null=True, upload_to="assets", verbose_name="logo 256x256" - ), + model_name='websettings', + name='logo_256', + field=models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo 256x256'), ), ] diff --git a/web/migrations/0003_websettings_theme_colors.py b/web/migrations/0003_websettings_theme_colors.py index 8de5b66..962bf85 100644 --- a/web/migrations/0003_websettings_theme_colors.py +++ b/web/migrations/0003_websettings_theme_colors.py @@ -4,17 +4,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - - dependencies = [ - ("web", "0002_remove_websettings_logo_240_websettings_logo_256"), - ] + dependencies = [('web', '0002_remove_websettings_logo_240_websettings_logo_256')] operations = [ migrations.AddField( - model_name="websettings", - name="theme_colors", - field=models.JSONField( - default=dict, verbose_name="colores del tema", blank=True - ), - ), + model_name='websettings', + name='theme_colors', + field=models.JSONField(default=dict, verbose_name='colores del tema', blank=True), + ) ] diff --git a/web/migrations/0004_websettings_logo_32_websettings_logo_64.py b/web/migrations/0004_websettings_logo_32_websettings_logo_64.py index 0631005..1939ec0 100644 --- a/web/migrations/0004_websettings_logo_32_websettings_logo_64.py +++ b/web/migrations/0004_websettings_logo_32_websettings_logo_64.py @@ -4,24 +4,17 @@ from django.db import migrations, models class Migration(migrations.Migration): - - dependencies = [ - ("web", "0003_websettings_theme_colors"), - ] + dependencies = [('web', '0003_websettings_theme_colors')] operations = [ migrations.AddField( - model_name="websettings", - name="logo_32", - field=models.ImageField( - blank=True, null=True, upload_to="assets", verbose_name="logo 32x32" - ), + model_name='websettings', + name='logo_32', + field=models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo 32x32'), ), migrations.AddField( - model_name="websettings", - name="logo_64", - field=models.ImageField( - blank=True, null=True, upload_to="assets", verbose_name="logo 64x64" - ), + model_name='websettings', + name='logo_64', + field=models.ImageField(blank=True, null=True, upload_to='assets', verbose_name='logo 64x64'), ), ] diff --git a/web/mixins.py b/web/mixins.py index 16dbe5f..1955fef 100644 --- a/web/mixins.py +++ b/web/mixins.py @@ -22,7 +22,7 @@ class PaginatedQuerysetMixin: def get_paginated_queryset(self, qs): paginator = Paginator(qs, self.objects_per_page) - page = self.request.GET.get("page", 1) + page = self.request.GET.get('page', 1) qs = paginator.get_page(page) return qs @@ -30,9 +30,7 @@ class PaginatedQuerysetMixin: class StylingMixin: placeholder_for_field = {} styled_fields = [] - classes = { - "input-text", - } + classes = {'input-text'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -42,11 +40,7 @@ class StylingMixin: for field_name in self.styled_fields: field = self.fields[field_name] field.required = True - field.widget.attrs.update({"class": " ".join(self.classes)}) + field.widget.attrs.update({'class': ' '.join(self.classes)}) if self.placeholder_for_field.get(field_name): - field.widget.attrs.update( - { - "placeholder": self.placeholder_for_field.get(field_name), - } - ) + field.widget.attrs.update({'placeholder': self.placeholder_for_field.get(field_name)}) diff --git a/web/models.py b/web/models.py index 23e035c..a842f04 100644 --- a/web/models.py +++ b/web/models.py @@ -9,101 +9,57 @@ from config.models import SingletonModel class WebSettings(SingletonModel): - web_title = models.CharField( - max_length=32, blank=False, verbose_name=_("título de la web") - ) - web_description = models.CharField( - max_length=128, blank=True, default="", verbose_name=_("descripción de la web") - ) - logo = models.ImageField( - blank=True, null=True, upload_to="assets", verbose_name=_("logo de la web") - ) + web_title = models.CharField(max_length=32, blank=False, verbose_name=_('título de la web')) + web_description = models.CharField(max_length=128, blank=True, default='', verbose_name=_('descripción de la web')) + logo = models.ImageField(blank=True, null=True, upload_to='assets', verbose_name=_('logo de la web')) business_name = models.CharField( max_length=128, blank=True, null=True, - verbose_name=_("nombre de negocio"), - help_text=_("nombre fiscal del comercio"), - ) - business_vat_id = models.CharField( - max_length=16, blank=True, null=True, verbose_name=_("CIF del negocio") + verbose_name=_('nombre de negocio'), + help_text=_('nombre fiscal del comercio'), ) + business_vat_id = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('CIF del negocio')) business_brand = models.CharField( max_length=64, blank=True, null=True, - verbose_name=_("marca de negocio"), - help_text=_("marca o nombre comercial que se va a mostrar en la web"), + verbose_name=_('marca de negocio'), + help_text=_('marca o nombre comercial que se va a mostrar en la web'), ) - business_address = models.CharField( - max_length=64, blank=True, null=True, verbose_name=_("dirección de negocio") - ) + business_address = models.CharField(max_length=64, blank=True, null=True, verbose_name=_('dirección de negocio')) business_state = models.CharField( - max_length=32, - blank=True, - null=True, - verbose_name=_("unidad territorial del negocio"), - ) - business_zip = models.CharField( - max_length=16, - blank=True, - null=True, - verbose_name=_("código postal del negocio"), + max_length=32, blank=True, null=True, verbose_name=_('unidad territorial del negocio') ) + business_zip = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('código postal del negocio')) business_phone = models.CharField( - max_length=16, - blank=True, - null=True, - verbose_name=_("número de teléfono de contacto"), - ) - business_email = models.EmailField( - max_length=80, blank=True, null=True, verbose_name=_("e-mail de contacto") + max_length=16, blank=True, null=True, verbose_name=_('número de teléfono de contacto') ) + business_email = models.EmailField(max_length=80, blank=True, null=True, verbose_name=_('e-mail de contacto')) business_email_2 = models.EmailField( - max_length=80, - blank=True, - null=True, - verbose_name=_("e-mail secundario de contacto"), + max_length=80, blank=True, null=True, verbose_name=_('e-mail secundario de contacto') ) - bg_color = models.CharField( - max_length=9, - default="", - blank=True, - null=True, - verbose_name=_("color de fondo"), - ) - theme_color = models.CharField( - max_length=9, default="", blank=True, null=True, verbose_name=_("color de tema") - ) + bg_color = models.CharField(max_length=9, default='', blank=True, null=True, verbose_name=_('color de fondo')) + theme_color = models.CharField(max_length=9, default='', blank=True, null=True, verbose_name=_('color de tema')) - logo_256 = models.ImageField( - upload_to="assets", blank=True, null=True, verbose_name=_("logo 256x256") - ) - logo_128 = models.ImageField( - upload_to="assets", blank=True, null=True, verbose_name=_("logo 128x128") - ) - logo_64 = models.ImageField( - upload_to="assets", blank=True, null=True, verbose_name=_("logo 64x64") - ) - logo_32 = models.ImageField( - upload_to="assets", blank=True, null=True, verbose_name=_("logo 32x32") - ) - theme_colors = models.JSONField( - default=dict, verbose_name=_("colores del tema"), blank=True - ) + logo_256 = models.ImageField(upload_to='assets', blank=True, null=True, verbose_name=_('logo 256x256')) + logo_128 = models.ImageField(upload_to='assets', blank=True, null=True, verbose_name=_('logo 128x128')) + logo_64 = models.ImageField(upload_to='assets', blank=True, null=True, verbose_name=_('logo 64x64')) + logo_32 = models.ImageField(upload_to='assets', blank=True, null=True, verbose_name=_('logo 32x32')) + theme_colors = models.JSONField(default=dict, verbose_name=_('colores del tema'), blank=True) def get_resized_logo(self, size): width, height = size image = Image.open(self.logo) image = image.resize(size, Image.Resampling.LANCZOS) buffer = BytesIO() - image.save(fp=buffer, format="PNG") + image.save(fp=buffer, format='PNG') - return ContentFile(buffer.getvalue(), name=f"logo_{width}.png") + return ContentFile(buffer.getvalue(), name=f'logo_{width}.png') def save(self, *args, **kwargs): if self.logo: @@ -115,5 +71,5 @@ class WebSettings(SingletonModel): super().save(*args, **kwargs) class Meta: - verbose_name = _("ajustes de la web") - verbose_name_plural = _("ajustes de la web") + verbose_name = _('ajustes de la web') + verbose_name_plural = _('ajustes de la web') diff --git a/web/settings.py b/web/settings.py index 525f52f..f5b5f0d 100644 --- a/web/settings.py +++ b/web/settings.py @@ -1 +1 @@ -ANONYMOUS_CART_ID_COOKIE_NAME = "caid" +ANONYMOUS_CART_ID_COOKIE_NAME = 'caid' diff --git a/web/signals.py b/web/signals.py index 1a75ca5..c0c3214 100644 --- a/web/signals.py +++ b/web/signals.py @@ -6,13 +6,13 @@ from shop.signals import clear_cart, send_order_email from web.models import WebSettings -@receiver(clear_cart, dispatch_uid="clear_cart") -def create_payment_from_redsys(sender, **kwargs): - cart = Cart.objects.get(hash=kwargs.get("cart_id")) +@receiver(clear_cart, dispatch_uid='clear_cart') +def clear_cart(sender, **kwargs): + cart = Cart.objects.get(hash=kwargs.get('cart_id')) CartItem.objects.filter(cart=cart).delete() -@receiver(send_order_email, dispatch_uid="send_order_email") +@receiver(send_order_email, dispatch_uid='send_order_email') def create_payment_from_redsys(sender, **kwargs): pass @@ -20,6 +20,6 @@ def create_payment_from_redsys(sender, **kwargs): @receiver(post_save, sender=WebSettings) def rebuild_theme(sender, instance: WebSettings, **kwargs): try: - call_command("build_tailwind_theme") + call_command('build_tailwind_theme') except Exception: pass diff --git a/web/tests/test_cart.py b/web/tests/test_cart.py index 1b301b0..e0300be 100644 --- a/web/tests/test_cart.py +++ b/web/tests/test_cart.py @@ -10,22 +10,20 @@ from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME class TestCart(TestCase, CreateProductsMixin): def setUp(self): self.product = self.create_product() - self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand") + self.user = User.objects.create_user('vader', 'darth@vader.com', 'ihatesand') def test_anonymous_cart(self): assert Cart.objects.count() == 0 - response = self.client.get(reverse("web:cart_dropdown")) + response = self.client.get(reverse('web:cart_dropdown')) assert response.status_code == 200 cart = Cart.objects.all().first() assert cart is not None - assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str( - cart.uuid - ) + assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str(cart.uuid) def test_logged_user_without_cart(self): assert Cart.objects.count() == 0 self.client.force_login(self.user) - response = self.client.get(reverse("web:cart_dropdown")) + response = self.client.get(reverse('web:cart_dropdown')) assert response.status_code == 200 assert Cart.objects.count() == 1 assert not response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME) @@ -33,7 +31,7 @@ class TestCart(TestCase, CreateProductsMixin): def test_logged_user_with_cart(self): Cart.objects.create(user=self.user) self.client.force_login(self.user) - response = self.client.get(reverse("web:cart_dropdown")) + response = self.client.get(reverse('web:cart_dropdown')) assert response.status_code == 200 assert not response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME) @@ -41,17 +39,12 @@ class TestCart(TestCase, CreateProductsMixin): quantity = 2 assert Cart.objects.count() == 0 - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() assert cart is not None - assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str( - cart.uuid - ) + assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str(cart.uuid) cart_item = CartItem.objects.filter(cart=cart).first() assert cart_item is not None @@ -63,10 +56,7 @@ class TestCart(TestCase, CreateProductsMixin): assert Cart.objects.count() == 0 self.client.force_login(self.user) - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() @@ -83,10 +73,7 @@ class TestCart(TestCase, CreateProductsMixin): Cart.objects.create(user=self.user) self.client.force_login(self.user) - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() @@ -103,16 +90,10 @@ class TestCart(TestCase, CreateProductsMixin): Cart.objects.create(user=self.user) self.client.force_login(self.user) - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() @@ -129,17 +110,12 @@ class TestCart(TestCase, CreateProductsMixin): quantity = 2 assert Cart.objects.count() == 0 - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() assert cart is not None - assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str( - cart.uuid - ) + assert response.cookies.get(ANONYMOUS_CART_ID_COOKIE_NAME).value == str(cart.uuid) cart_item = CartItem.objects.filter(cart=cart).first() assert cart_item is not None @@ -147,8 +123,8 @@ class TestCart(TestCase, CreateProductsMixin): assert cart_item.quantity == quantity response = self.client.post( - reverse("web:delete_cart_item", kwargs={"pk": cart_item.pk}), - {"product": self.product.pk, "quantity": quantity}, + reverse('web:delete_cart_item', kwargs={'pk': cart_item.pk}), + {'product': self.product.pk, 'quantity': quantity}, ) assert response.status_code == 204 assert CartItem.objects.filter(cart=cart).count() == 0 @@ -158,10 +134,7 @@ class TestCart(TestCase, CreateProductsMixin): assert Cart.objects.count() == 0 self.client.force_login(self.user) - response = self.client.post( - reverse("web:add_cart_item"), - {"product": self.product.pk, "quantity": quantity}, - ) + response = self.client.post(reverse('web:add_cart_item'), {'product': self.product.pk, 'quantity': quantity}) assert response.status_code == 201 cart = Cart.objects.all().first() @@ -174,15 +147,15 @@ class TestCart(TestCase, CreateProductsMixin): assert cart_item.quantity == quantity response = self.client.post( - reverse("web:delete_cart_item", kwargs={"pk": cart_item.pk}), - {"product": self.product.pk, "quantity": quantity}, + reverse('web:delete_cart_item', kwargs={'pk': cart_item.pk}), + {'product': self.product.pk, 'quantity': quantity}, ) assert response.status_code == 204 assert CartItem.objects.filter(cart=cart).count() == 0 def test_anonymous_cart_detail_component(self): assert Cart.objects.count() == 0 - response = self.client.get(reverse("web:cart")) + response = self.client.get(reverse('web:cart')) assert response.status_code == 200 assert Cart.objects.count() == 1 @@ -191,5 +164,5 @@ class TestCart(TestCase, CreateProductsMixin): cart = Cart.objects.create(user=self.user) CartItem.objects.create(cart=cart, product=self.product, quantity=1) self.client.force_login(self.user) - response = self.client.get(reverse("web:cart")) + response = self.client.get(reverse('web:cart')) assert response.status_code == 200 diff --git a/web/tests/test_category_view.py b/web/tests/test_category_view.py index 6bd66f7..1bac0ab 100644 --- a/web/tests/test_category_view.py +++ b/web/tests/test_category_view.py @@ -10,10 +10,6 @@ class TestCategoryView(TestCase, CreateProductsMixin): self.product = self.create_product() def test_category_view(self): - category = ProductCategory.objects.create( - name="category 1", show_in_navbar=True, hidden=False - ) - response = self.client.get( - reverse("web:category_view", kwargs={"slug": category.slug}) - ) + category = ProductCategory.objects.create(name='category 1', show_in_navbar=True, hidden=False) + response = self.client.get(reverse('web:category_view', kwargs={'slug': category.slug})) assert response.status_code == 200 diff --git a/web/tests/test_change_user_password.py b/web/tests/test_change_user_password.py index c4cd994..af9d0bb 100644 --- a/web/tests/test_change_user_password.py +++ b/web/tests/test_change_user_password.py @@ -7,22 +7,18 @@ from shop.tests.mixins import CreateProductsMixin class TestChangeUserPassword(TestCase, CreateProductsMixin): def setUp(self): - self.password = "ihatesand" - self.user = User.objects.create_user("vader", "darth@vader.com", self.password) + self.password = 'ihatesand' + self.user = User.objects.create_user('vader', 'darth@vader.com', self.password) def test_update_user_info(self): self.client.force_login(self.user) - response = self.client.get(reverse("web:change_password_component")) + response = self.client.get(reverse('web:change_password_component')) assert response.status_code == 200 - new_password = "ifnhatesandbro" - data = { - "old_password": self.password, - "new_password1": new_password, - "new_password2": new_password, - } + new_password = 'ifnhatesandbro' + data = {'old_password': self.password, 'new_password1': new_password, 'new_password2': new_password} - response = self.client.post(reverse("web:change_password_component"), data) + response = self.client.post(reverse('web:change_password_component'), data) assert response.status_code == 200 self.user.refresh_from_db() diff --git a/web/tests/test_components.py b/web/tests/test_components.py index f862ddf..6b761dc 100644 --- a/web/tests/test_components.py +++ b/web/tests/test_components.py @@ -9,5 +9,5 @@ class TestWebComponents(TestCase, CreateProductsMixin): self.product = self.create_product() def test_list_products_component(self): - response = self.client.get(reverse("web:list_products")) + response = self.client.get(reverse('web:list_products')) assert response.status_code == 200 diff --git a/web/tests/test_customer_addresses.py b/web/tests/test_customer_addresses.py index 6afa201..63fe445 100644 --- a/web/tests/test_customer_addresses.py +++ b/web/tests/test_customer_addresses.py @@ -8,43 +8,40 @@ from shop.tests.mixins import CreateProductsMixin class TestCustomerAddresses(TestCase, CreateProductsMixin): def setUp(self): - self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand") + self.user = User.objects.create_user('vader', 'darth@vader.com', 'ihatesand') self.customer_address = CustomerAddress.objects.create( user=self.user, - full_name="Darth Vader", + full_name='Darth Vader', address_type=CustomerAddress.Types.SHIPPING, - address="Vader Fortress", - address_town="Mustafar", - address_country="Mustafar", - address_state="Mustafar", - address_phone="612345678", - address_zip="00001", + address='Vader Fortress', + address_town='Mustafar', + address_country='Mustafar', + address_state='Mustafar', + address_phone='612345678', + address_zip='00001', ) def test_update_customer_address(self): self.client.force_login(self.user) data = { - "full_name": "Anakin Skywalker", - "address": "Mos Eisley", - "address_town": "Mos Eisley", - "address_country": "Tatooine", - "address_state": "Tatooine", - "address_phone": "612345678", - "address_zip": "00001", + 'full_name': 'Anakin Skywalker', + 'address': 'Mos Eisley', + 'address_town': 'Mos Eisley', + 'address_country': 'Tatooine', + 'address_state': 'Tatooine', + 'address_phone': '612345678', + 'address_zip': '00001', } response = self.client.post( - reverse( - "web:update_customer_address", kwargs={"pk": self.customer_address.pk} - ), - data, + reverse('web:update_customer_address', kwargs={'pk': self.customer_address.pk}), data ) assert response.status_code == 200 self.customer_address.refresh_from_db() - assert self.customer_address.full_name == data.get("full_name") - assert self.customer_address.address == data.get("address") - assert self.customer_address.address_town == data.get("address_town") - assert self.customer_address.address_state == data.get("address_state") - assert self.customer_address.address_phone == data.get("address_phone") + assert self.customer_address.full_name == data.get('full_name') + assert self.customer_address.address == data.get('address') + assert self.customer_address.address_town == data.get('address_town') + assert self.customer_address.address_state == data.get('address_state') + assert self.customer_address.address_phone == data.get('address_phone') diff --git a/web/tests/test_index.py b/web/tests/test_index.py index 6103ae6..33e98f1 100644 --- a/web/tests/test_index.py +++ b/web/tests/test_index.py @@ -9,14 +9,11 @@ class TestIndex(TestCase, CreateProductsMixin): self.product = self.create_product() def test_index_view(self): - response = self.client.get(reverse("web:index")) + response = self.client.get(reverse('web:index')) assert response.status_code == 200 def test_product_detail_view(self): response = self.client.get( - reverse( - "web:product_detail", - kwargs={"pk": self.product.pk, "slug": self.product.slug}, - ) + reverse('web:product_detail', kwargs={'pk': self.product.pk, 'slug': self.product.slug}) ) assert response.status_code == 200 diff --git a/web/tests/test_login.py b/web/tests/test_login.py index e53dd88..a80c6a6 100644 --- a/web/tests/test_login.py +++ b/web/tests/test_login.py @@ -7,142 +7,91 @@ User = get_user_model() class TestLogin(TestCase): def setUp(self) -> None: - self.password = "theonering" - self.user = User.objects.create( - username="sauron", - email="sauron@mordor.middleearth", - ) + self.password = 'theonering' + self.user = User.objects.create(username='sauron', email='sauron@mordor.middleearth') self.user.set_password(self.password) self.user.save() def test_login(self): - response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(reverse('web:login'), {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') def test_login_get(self): - response = self.client.get(reverse("web:login")) + response = self.client.get(reverse('web:login')) assert response.status_code == 200 def test_login_already_logged_in(self): - response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(reverse('web:login'), {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') - response = self.client.get(reverse("web:login")) + response = self.client.get(reverse('web:login')) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') def test_login_failed(self): response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": "wrongpassword", - }, + reverse('web:login'), {'username': self.user.username, 'password': 'wrongpassword'} ) assert response.status_code == 400 def test_login_then_logout(self): - response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(reverse('web:login'), {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') - response = self.client.post(reverse("web:logout")) + response = self.client.post(reverse('web:logout')) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') def test_logout_already_logged_out(self): - response = self.client.post(reverse("web:logout")) + response = self.client.post(reverse('web:logout')) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') def test_login_then_get_logout(self): - response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(reverse('web:login'), {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') - response = self.client.get(reverse("web:logout")) + response = self.client.get(reverse('web:logout')) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') def test_login_then_get_my_account(self): - response = self.client.post( - reverse("web:login"), - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(reverse('web:login'), {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert response.url == reverse("web:index") + assert response.url == reverse('web:index') - response = self.client.get(reverse("web:my_account")) + response = self.client.get(reverse('web:my_account')) assert response.status_code == 200 def test_login_redirection_to_my_account(self): url = f'{reverse("web:login")}?next={reverse("web:my_account")}' - response = self.client.post( - url, - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(url, {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 - assert ( - response.url == f'{reverse("web:index")}?next={reverse("web:my_account")}' - ) + assert response.url == f'{reverse("web:index")}?next={reverse("web:my_account")}' response = self.client.get(response.url) assert response.status_code == 302 - assert response.url == reverse("web:my_account") + assert response.url == reverse('web:my_account') def test_login_redirection_to_my_orders(self): url = f'{reverse("web:login")}?next={reverse("web:orders")}' - response = self.client.post( - url, - { - "username": self.user.username, - "password": self.password, - }, - ) + response = self.client.post(url, {'username': self.user.username, 'password': self.password}) assert response.status_code == 302 assert response.url == f'{reverse("web:index")}?next={reverse("web:orders")}' response = self.client.get(response.url) assert response.status_code == 302 - assert response.url == reverse("web:orders") + assert response.url == reverse('web:orders') diff --git a/web/tests/test_manifest.py b/web/tests/test_manifest.py index 4ce126a..b2953a3 100644 --- a/web/tests/test_manifest.py +++ b/web/tests/test_manifest.py @@ -12,14 +12,11 @@ class TestManifest(TestCase): def setUp(self): self.settings = WebSettings.load() - with open( - os.path.join(settings.BASE_DIR, "web", "tests", "images", "trolley.png"), - "rb", - ) as f: - file = SimpleUploadedFile("logo.png", f.read()) + with open(os.path.join(settings.BASE_DIR, 'web', 'tests', 'images', 'trolley.png'), 'rb') as f: + file = SimpleUploadedFile('logo.png', f.read()) self.settings.logo = file self.settings.save() def test_manifest_view(self): - response = self.client.get(reverse("web:manifest_json")) + response = self.client.get(reverse('web:manifest_json')) assert response.status_code == 200 diff --git a/web/tests/test_orders.py b/web/tests/test_orders.py index d8415f7..3791a9e 100644 --- a/web/tests/test_orders.py +++ b/web/tests/test_orders.py @@ -4,91 +4,76 @@ from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse -from shop.models import ( - Cart, - CartItem, - CustomerAddress, - Order, - ShippingMethod, - ShopSettings, -) +from shop.models import Cart, CartItem, CustomerAddress, Order, ShippingMethod, ShopSettings from shop.tests.mixins import CreateProductsMixin class TestOrders(TestCase, CreateProductsMixin): def setUp(self): self.product = self.create_product() - self.user = User.objects.create_user( - "anakin", "anakin@skywalker.com", "ihatesand" - ) + self.user = User.objects.create_user('anakin', 'anakin@skywalker.com', 'ihatesand') self.cart_for_user = self.create_cart(user=self.user) self.anonymous_cart = self.create_cart() self.address = CustomerAddress.objects.create( - vat_id="11111111H", - full_name="Vader", - address="Tatooine", - address_town="Mos Eisley", - address_zip="00001", - address_state="Tatooine", - address_country="Mos Eisley", - address_phone="00000000", + vat_id='11111111H', + full_name='Vader', + address='Tatooine', + address_town='Mos Eisley', + address_zip='00001', + address_state='Tatooine', + address_country='Mos Eisley', + address_phone='00000000', address_type=CustomerAddress.Types.BILLING, email=self.user.email, default=True, ) self.shipping_method = self.create_shipping_method() self.settings = ShopSettings.load() - self.settings.shared_secret = "sq7HjrUOBfKmC576ILgskD5srU870gJ7" # Debug secret + self.settings.shared_secret = 'sq7HjrUOBfKmC576ILgskD5srU870gJ7' # Debug secret self.settings.save() def create_cart(self, user=None): - cart = Cart.objects.create( - user=user, - ) - CartItem.objects.create( - cart=cart, - product=self.product, - quantity=1, - ) + cart = Cart.objects.create(user=user) + CartItem.objects.create(cart=cart, product=self.product, quantity=1) return cart def create_shipping_method(self): - product = self.create_product(sku="SM1", name="SM1", price=Decimal("1.00")) - method = ShippingMethod.objects.create(name="SM 1", shipping_product=product) + product = self.create_product(sku='SM1', name='SM1', price=Decimal('1.00')) + method = ShippingMethod.objects.create(name='SM 1', shipping_product=product) return method def test_logged_user_orders_page(self): self.client.force_login(self.user) - response = self.client.get(reverse("web:orders")) + response = self.client.get(reverse('web:orders')) assert response.status_code == 200 def test_logged_user_create_get_order_page(self): self.client.force_login(self.user) - response = self.client.get(reverse("web:cart_detail")) + response = self.client.get(reverse('web:cart_detail')) assert response.status_code == 200 # Crear pedido data = { - "email": self.user.email, - "shipping_address_full_name": self.address.full_name, - "shipping_address": self.address.address, - "shipping_address_town": self.address.address_town, - "shipping_address_zip": self.address.address_zip, - "shipping_address_state": self.address.address_state, - "shipping_address_country": self.address.address_country, - "shipping_address_phone": self.address.address_phone, - "same_as_shipping": True, - "billing_address_full_name": self.address.full_name, - "billing_address": self.address.address, - "billing_address_town": self.address.address_town, - "billing_address_zip": self.address.address_zip, - "billing_address_state": self.address.address_state, - "billing_address_country": self.address.address_country, - "billing_address_phone": self.address.address_phone, - "shipping_method": self.shipping_method.pk, + 'email': self.user.email, + 'shipping_address_full_name': self.address.full_name, + 'shipping_address': self.address.address, + 'shipping_address_town': self.address.address_town, + 'shipping_address_zip': self.address.address_zip, + 'shipping_address_state': self.address.address_state, + 'shipping_address_country': self.address.address_country, + 'shipping_address_phone': self.address.address_phone, + 'same_as_shipping': True, + 'billing_address_full_name': self.address.full_name, + 'billing_address': self.address.address, + 'billing_address_town': self.address.address_town, + 'billing_address_zip': self.address.address_zip, + 'billing_address_state': self.address.address_state, + 'billing_address_country': self.address.address_country, + 'billing_address_phone': self.address.address_phone, + 'shipping_method': self.shipping_method.pk, } - response = self.client.post(reverse("web:cart_detail"), data) + response = self.client.post(reverse('web:cart_detail'), data) assert response.status_code == 302 order = Order.objects.filter(user=self.user).first() @@ -99,30 +84,30 @@ class TestOrders(TestCase, CreateProductsMixin): def test_logged_user_create_get_order_page_invalid_data(self): self.client.force_login(self.user) - response = self.client.get(reverse("web:cart_detail")) + response = self.client.get(reverse('web:cart_detail')) assert response.status_code == 200 # Sin e-mail data = { - "shipping_address_full_name": self.address.full_name, - "shipping_address": self.address.address, - "shipping_address_town": self.address.address_town, - "shipping_address_zip": self.address.address_zip, - "shipping_address_state": self.address.address_state, - "shipping_address_country": self.address.address_country, - "shipping_address_phone": self.address.address_phone, - "same_as_shipping": True, - "billing_address_full_name": self.address.full_name, - "billing_address": self.address.address, - "billing_address_town": self.address.address_town, - "billing_address_zip": self.address.address_zip, - "billing_address_state": self.address.address_state, - "billing_address_country": self.address.address_country, - "billing_address_phone": self.address.address_phone, - "shipping_method": self.shipping_method.pk, + 'shipping_address_full_name': self.address.full_name, + 'shipping_address': self.address.address, + 'shipping_address_town': self.address.address_town, + 'shipping_address_zip': self.address.address_zip, + 'shipping_address_state': self.address.address_state, + 'shipping_address_country': self.address.address_country, + 'shipping_address_phone': self.address.address_phone, + 'same_as_shipping': True, + 'billing_address_full_name': self.address.full_name, + 'billing_address': self.address.address, + 'billing_address_town': self.address.address_town, + 'billing_address_zip': self.address.address_zip, + 'billing_address_state': self.address.address_state, + 'billing_address_country': self.address.address_country, + 'billing_address_phone': self.address.address_phone, + 'shipping_method': self.shipping_method.pk, } - response = self.client.post(reverse("web:cart_detail"), data) + response = self.client.post(reverse('web:cart_detail'), data) assert response.status_code == 200 assert not Order.objects.filter(user=self.user).exists() diff --git a/web/tests/test_register.py b/web/tests/test_register.py index ee7b7f6..6fae97b 100644 --- a/web/tests/test_register.py +++ b/web/tests/test_register.py @@ -7,124 +7,106 @@ User = get_user_model() class TestRegister(TransactionTestCase): def setUp(self) -> None: - self.password = "theonering" - self.email = "sauron@mordor.middleearth" - self.first_name = "Sauron" - self.last_name = "The Lord of the Rings" + self.password = 'theonering' + self.email = 'sauron@mordor.middleearth' + self.first_name = 'Sauron' + self.last_name = 'The Lord of the Rings' def test_get_register_page(self): - response = self.client.get(reverse("web:register")) + response = self.client.get(reverse('web:register')) assert response.status_code == 200 def test_create_user_account(self): response = self.client.post( - reverse("web:register"), + reverse('web:register'), { - "email": self.email, - "password1": self.password, - "password2": self.password, - "first_name": self.first_name, - "last_name": self.last_name, + 'email': self.email, + 'password1': self.password, + 'password2': self.password, + 'first_name': self.first_name, + 'last_name': self.last_name, }, ) user = User.objects.filter( - username=self.email, - email=self.email, - first_name=self.first_name, - last_name=self.last_name, + username=self.email, email=self.email, first_name=self.first_name, last_name=self.last_name ).first() assert user is not None assert response.status_code == 302 - assert response.url == reverse("web:login") + assert response.url == reverse('web:login') def test_create_user_account_distinct_password(self): response = self.client.post( - reverse("web:register"), + reverse('web:register'), { - "email": self.email, - "password1": self.password, - "password2": "otherpassword", - "first_name": self.first_name, - "last_name": self.last_name, + 'email': self.email, + 'password1': self.password, + 'password2': 'otherpassword', + 'first_name': self.first_name, + 'last_name': self.last_name, }, ) assert not User.objects.filter( - username=self.email, - email=self.email, - first_name=self.first_name, - last_name=self.last_name, + username=self.email, email=self.email, first_name=self.first_name, last_name=self.last_name ).exists() assert response.status_code == 400 def test_create_user_account_no_first_name(self): response = self.client.post( - reverse("web:register"), + reverse('web:register'), { - "email": self.email, - "password1": self.password, - "password2": "otherpassword", - "first_name": "", - "last_name": self.last_name, + 'email': self.email, + 'password1': self.password, + 'password2': 'otherpassword', + 'first_name': '', + 'last_name': self.last_name, }, ) assert not User.objects.filter( - username=self.email, - email=self.email, - first_name="", - last_name=self.last_name, + username=self.email, email=self.email, first_name='', last_name=self.last_name ).exists() assert response.status_code == 400 def test_create_user_account_no_last_name(self): response = self.client.post( - reverse("web:register"), + reverse('web:register'), { - "email": self.email, - "password1": self.password, - "password2": self.password, - "first_name": self.first_name, - "last_name": "", + 'email': self.email, + 'password1': self.password, + 'password2': self.password, + 'first_name': self.first_name, + 'last_name': '', }, ) assert not User.objects.filter( - username=self.email, - email=self.email, - first_name=self.first_name, - last_name="", + username=self.email, email=self.email, first_name=self.first_name, last_name='' ).exists() assert response.status_code == 400 def test_create_user_account_already_existing(self): assert User.objects.all().count() == 0 - User.objects.create_user( - email=self.email, - username=self.email, - ) + User.objects.create_user(email=self.email, username=self.email) response = self.client.post( - reverse("web:register"), + reverse('web:register'), { - "email": self.email, - "password1": self.password, - "password2": self.password, - "first_name": self.first_name, - "last_name": self.last_name, + 'email': self.email, + 'password1': self.password, + 'password2': self.password, + 'first_name': self.first_name, + 'last_name': self.last_name, }, ) assert not User.objects.filter( - username=self.email, - email=self.email, - first_name=self.first_name, - last_name="", + username=self.email, email=self.email, first_name=self.first_name, last_name='' ).exists() assert response.status_code == 400 diff --git a/web/tests/test_reset_password.py b/web/tests/test_reset_password.py index 5a47705..e0b3bf7 100644 --- a/web/tests/test_reset_password.py +++ b/web/tests/test_reset_password.py @@ -10,53 +10,36 @@ User = get_user_model() class TestResetPassword(TransactionTestCase): def setUp(self) -> None: - self.password = "theonering" - self.email = "sauron@mordor.middleearth" - self.first_name = "Sauron" - self.last_name = "The Lord of the Rings" + self.password = 'theonering' + self.email = 'sauron@mordor.middleearth' + self.first_name = 'Sauron' + self.last_name = 'The Lord of the Rings' self.user = User.objects.create_user( - username=self.email, - password=self.password, - first_name=self.first_name, - last_name=self.last_name, + username=self.email, password=self.password, first_name=self.first_name, last_name=self.last_name ) def test_get_reset_password_page(self): - response = self.client.get(reverse("web:reset_password")) + response = self.client.get(reverse('web:reset_password')) assert response.status_code == 200 def test_reset_password(self): - response = self.client.post( - reverse("web:reset_password"), - { - "email": self.email, - }, - ) + response = self.client.post(reverse('web:reset_password'), {'email': self.email}) assert response.status_code == 302 - assert response.url == reverse("web:reset_password_email_sent") + assert response.url == reverse('web:reset_password_email_sent') def test_reset_password_not_existing_user(self): - response = self.client.post( - reverse("web:reset_password"), - { - "email": "vanpiro@noesiten.com", - }, - ) + response = self.client.post(reverse('web:reset_password'), {'email': 'vanpiro@noesiten.com'}) assert response.status_code == 302 - assert response.url == reverse("web:reset_password_email_sent") + assert response.url == reverse('web:reset_password_email_sent') def test_reset_password_confirm_get(self): token = default_token_generator.make_token(self.user) uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) - response = self.client.get( - reverse( - "web:reset_password_confirm", kwargs={"uidb64": uidb64, "token": token} - ), - ) + response = self.client.get(reverse('web:reset_password_confirm', kwargs={'uidb64': uidb64, 'token': token})) assert response.status_code == 200 @@ -65,20 +48,14 @@ class TestResetPassword(TransactionTestCase): form_token = default_token_generator.make_token(self.user) uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) - new_password = "89733wy29" + new_password = '89733wy29' response = self.client.post( - reverse( - "web:reset_password_confirm", kwargs={"uidb64": uidb64, "token": token} - ), - { - "token": form_token, - "new_password1": new_password, - "new_password2": new_password, - }, + reverse('web:reset_password_confirm', kwargs={'uidb64': uidb64, 'token': token}), + {'token': form_token, 'new_password1': new_password, 'new_password2': new_password}, ) assert response.status_code == 302 - assert response.url == reverse("web:login") + assert response.url == reverse('web:login') self.user.refresh_from_db() assert self.user.check_password(new_password) @@ -88,35 +65,23 @@ class TestResetPassword(TransactionTestCase): form_token = default_token_generator.make_token(self.user) uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) - new_password = "89733wy29" + new_password = '89733wy29' response = self.client.post( - reverse( - "web:reset_password_confirm", kwargs={"uidb64": uidb64, "token": token} - ), - { - "token": form_token, - "new_password1": new_password, - "new_password2": "otherpassw13", - }, + reverse('web:reset_password_confirm', kwargs={'uidb64': uidb64, 'token': token}), + {'token': form_token, 'new_password1': new_password, 'new_password2': 'otherpassw13'}, ) assert response.status_code == 200 def test_reset_password_confirm_invalid_token(self): token = default_token_generator.make_token(self.user) - form_token = "invalid token" + form_token = 'invalid token' uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)) - new_password = "89733wy29" + new_password = '89733wy29' response = self.client.post( - reverse( - "web:reset_password_confirm", kwargs={"uidb64": uidb64, "token": token} - ), - { - "token": form_token, - "new_password1": new_password, - "new_password2": new_password, - }, + reverse('web:reset_password_confirm', kwargs={'uidb64': uidb64, 'token': token}), + {'token': form_token, 'new_password1': new_password, 'new_password2': new_password}, ) assert response.status_code == 200 diff --git a/web/tests/test_user_info.py b/web/tests/test_user_info.py index 152f430..72a44ba 100644 --- a/web/tests/test_user_info.py +++ b/web/tests/test_user_info.py @@ -7,25 +7,21 @@ from shop.tests.mixins import CreateProductsMixin class TestUserInfo(TestCase, CreateProductsMixin): def setUp(self): - self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand") + self.user = User.objects.create_user('vader', 'darth@vader.com', 'ihatesand') def test_update_user_info(self): self.client.force_login(self.user) - response = self.client.get(reverse("web:user_info_component")) + response = self.client.get(reverse('web:user_info_component')) assert response.status_code == 200 - data = { - "first_name": "Anakin", - "last_name": "Skywalker", - "email": "anakin@skywalker.com", - } + data = {'first_name': 'Anakin', 'last_name': 'Skywalker', 'email': 'anakin@skywalker.com'} - response = self.client.post(reverse("web:user_info_component"), data) + response = self.client.post(reverse('web:user_info_component'), data) assert response.status_code == 200 self.user.refresh_from_db() - assert self.user.first_name == data.get("first_name") - assert self.user.last_name == data.get("last_name") - assert self.user.email == data.get("email") + assert self.user.first_name == data.get('first_name') + assert self.user.last_name == data.get('last_name') + assert self.user.email == data.get('email') diff --git a/web/tests/test_wishlist.py b/web/tests/test_wishlist.py index 8a2b500..554932b 100644 --- a/web/tests/test_wishlist.py +++ b/web/tests/test_wishlist.py @@ -9,77 +9,52 @@ from shop.tests.mixins import CreateProductsMixin class TestWishlist(TestCase, CreateProductsMixin): def setUp(self): self.product = self.create_product() - self.user = User.objects.create_user("vader", "darth@vader.com", "ihatesand") + self.user = User.objects.create_user('vader', 'darth@vader.com', 'ihatesand') def test_logged_user_wishlist_button_for_non_wishlisted_product(self): assert WishlistedProduct.objects.count() == 0 self.client.force_login(self.user) - response = self.client.get( - reverse("web:wishlist_button", kwargs={"pk": self.product.pk}) - ) + response = self.client.get(reverse('web:wishlist_button', kwargs={'pk': self.product.pk})) assert response.status_code == 200 def test_logged_user_wishlist_button_for_wishlisted_product(self): - WishlistedProduct.objects.create( - user=self.user, - product=self.product, - ) + WishlistedProduct.objects.create(user=self.user, product=self.product) self.client.force_login(self.user) - response = self.client.get( - reverse("web:wishlist_button", kwargs={"pk": self.product.pk}) - ) + response = self.client.get(reverse('web:wishlist_button', kwargs={'pk': self.product.pk})) assert response.status_code == 200 def test_logged_user_create_wishlisted_product(self): assert WishlistedProduct.objects.count() == 0 self.client.force_login(self.user) - response = self.client.post( - reverse("web:add_to_wishlist"), - { - "product": self.product.pk, - }, - ) + response = self.client.post(reverse('web:add_to_wishlist'), {'product': self.product.pk}) assert response.status_code == 201 - assert WishlistedProduct.objects.filter( - user=self.user, product=self.product - ).exists() + assert WishlistedProduct.objects.filter(user=self.user, product=self.product).exists() def test_not_logged_user_create_wishlisted_product(self): assert WishlistedProduct.objects.count() == 0 - response = self.client.post( - reverse("web:add_to_wishlist"), - { - "product": self.product.pk, - }, - ) + response = self.client.post(reverse('web:add_to_wishlist'), {'product': self.product.pk}) assert response.status_code == 400 def test_logged_user_delete_wishlisted_product(self): - wishlisted = WishlistedProduct.objects.create( - user=self.user, product=self.product - ) + wishlisted = WishlistedProduct.objects.create(user=self.user, product=self.product) self.client.force_login(self.user) - response = self.client.post( - reverse("web:delete_from_wishlist", kwargs={"pk": wishlisted.pk}) - ) + response = self.client.post(reverse('web:delete_from_wishlist', kwargs={'pk': wishlisted.pk})) assert response.status_code == 204 - assert not WishlistedProduct.objects.filter( - user=self.user, product=self.product - ).exists() + assert not WishlistedProduct.objects.filter(user=self.user, product=self.product).exists() def test_logged_user_wishlist(self): WishlistedProduct.objects.create(user=self.user, product=self.product) self.client.force_login(self.user) - response = self.client.get(reverse("web:wishlist")) + response = self.client.get(reverse('web:wishlist')) assert response.status_code == 200 def test_not_logged_user_wishlist(self): WishlistedProduct.objects.create(user=self.user, product=self.product) - response = self.client.get(reverse("web:wishlist")) + response = self.client.get(reverse('web:wishlist')) assert response.status_code == 302 def test_list_wishlisted_products(self): WishlistedProduct.objects.create(user=self.user, product=self.product) self.client.force_login(self.user) - response = self.client.get(reverse("web:list_wishlisted_products")) + response = self.client.get(reverse('web:list_wishlisted_products')) assert response.status_code == 200 diff --git a/web/urls.py b/web/urls.py index be01c13..798bf74 100644 --- a/web/urls.py +++ b/web/urls.py @@ -38,86 +38,52 @@ from web.views.web import ( wishlist, ) -app_name = "web" +app_name = 'web' i18n_resolvers = i18n_patterns( - path("", index, name="index"), - path( - _("wishlist"), login_required(wishlist, login_url=_("/login/")), name="wishlist" - ), - path(_("categories//"), category_view, name="category_view"), - path(_("products///"), product_detail, name="product_detail"), - path(_("cart/"), cart_detail, name="cart_detail"), - path(_("orders/"), login_required(orders, login_url=_("/login/")), name="orders"), - path(_("order//"), order_detail, name="order"), + path('', index, name='index'), + path(_('wishlist'), login_required(wishlist, login_url=_('/login/')), name='wishlist'), + path(_('categories//'), category_view, name='category_view'), + path(_('products///'), product_detail, name='product_detail'), + path(_('cart/'), cart_detail, name='cart_detail'), + path(_('orders/'), login_required(orders, login_url=_('/login/')), name='orders'), + path(_('order//'), order_detail, name='order'), # users - path(_("login/"), login, name="login"), - path(_("logout/"), logout, name="logout"), - path(_("register/"), register, name="register"), - path(_("reset-password/"), reset_password, name="reset_password"), - path( - _("reset-password///"), - reset_password_confirm, - name="reset_password_confirm", - ), - path( - _("reset-password/email-sent/"), - reset_password_email_sent, - name="reset_password_email_sent", - ), - path( - _("my-account/"), - login_required(my_account, login_url=_("/login/")), - name="my_account", - ), + path(_('login/'), login, name='login'), + path(_('logout/'), logout, name='logout'), + path(_('register/'), register, name='register'), + path(_('reset-password/'), reset_password, name='reset_password'), + path(_('reset-password///'), reset_password_confirm, name='reset_password_confirm'), + path(_('reset-password/email-sent/'), reset_password_email_sent, name='reset_password_email_sent'), + path(_('my-account/'), login_required(my_account, login_url=_('/login/')), name='my_account'), ) urlpatterns = [ # components - path("web/components/products/", list_products, name="list_products"), + path('web/components/products/', list_products, name='list_products'), + path('web/components/wishlist/', list_wishlisted_products, name='list_wishlisted_products'), + path('web/components/cart-dropdown/', cart_dropdown, name='cart_dropdown'), + path('web/components/cart/', cart, name='cart'), + path('web/components/wishlist-button//', wishlist_button, name='wishlist_button'), path( - "web/components/wishlist/", - list_wishlisted_products, - name="list_wishlisted_products", - ), - path("web/components/cart-dropdown/", cart_dropdown, name="cart_dropdown"), - path("web/components/cart/", cart, name="cart"), - path( - "web/components/wishlist-button//", - wishlist_button, - name="wishlist_button", + 'web/components/user-info/', + login_required(user_info_component, login_url=_('/login/')), + name='user_info_component', ), path( - "web/components/user-info/", - login_required(user_info_component, login_url=_("/login/")), - name="user_info_component", - ), - path( - "web/components/change-password/", - login_required(change_password_component, login_url=_("/login/")), - name="change_password_component", + 'web/components/change-password/', + login_required(change_password_component, login_url=_('/login/')), + name='change_password_component', ), # api - path("web/add-cart-item/", add_cart_item, name="add_cart_item"), - path("web/delete-cart-item//", delete_cart_item, name="delete_cart_item"), - path("web/add-to-wishlist/", add_to_wishlist, name="add_to_wishlist"), - path( - "web/delete-from-wishlist//", - delete_from_wishlist, - name="delete_from_wishlist", - ), - path( - "web/customer-address//", - update_customer_address, - name="update_customer_address", - ), - path( - "web/customer-addresses/", - list_customer_addresses_component, - name="list_customer_addresses_component", - ), + path('web/add-cart-item/', add_cart_item, name='add_cart_item'), + path('web/delete-cart-item//', delete_cart_item, name='delete_cart_item'), + path('web/add-to-wishlist/', add_to_wishlist, name='add_to_wishlist'), + path('web/delete-from-wishlist//', delete_from_wishlist, name='delete_from_wishlist'), + path('web/customer-address//', update_customer_address, name='update_customer_address'), + path('web/customer-addresses/', list_customer_addresses_component, name='list_customer_addresses_component'), # manifest - path("manifest.json", manifest, name="manifest_json"), + path('manifest.json', manifest, name='manifest_json'), ] for resolver in i18n_resolvers: diff --git a/web/views/components.py b/web/views/components.py index 032228d..251e2fb 100644 --- a/web/views/components.py +++ b/web/views/components.py @@ -10,14 +10,7 @@ from django.views.generic import TemplateView from config.mixins import HTMXFormComponent from shop.filters import ProductFilter -from shop.models import ( - CartItem, - CustomerAddress, - Product, - ProductPrice, - ShippingMethod, - WishlistedProduct, -) +from shop.models import CartItem, CustomerAddress, Product, ProductPrice, ShippingMethod, WishlistedProduct from users.forms.change_password import ChangePasswordForm from users.forms.info import UserInfoForm from web.forms import CustomerAddressForm @@ -27,10 +20,9 @@ from web.utils import get_or_create_cart class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): - template_name = "web/list_products.html" + template_name = 'web/list_products.html' queryset = Product.objects.filter(hidden=False).prefetch_related( - "images", - Prefetch("prices", queryset=ProductPrice.objects.filter(current=True), to_attr="current_prices") + 'images', Prefetch('prices', queryset=ProductPrice.objects.filter(current=True), to_attr='current_prices') ) filter_class = ProductFilter @@ -39,27 +31,21 @@ class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): page = self.get_paginated_queryset(qs) return { - "current_page": page.number, - "num_pages": page.paginator.num_pages, - "page": page, - "has_next_page": page.has_next(), - "has_previous_page": page.has_previous(), + 'current_page': page.number, + 'num_pages': page.paginator.num_pages, + 'page': page, + 'has_next_page': page.has_next(), + 'has_previous_page': page.has_previous(), } -class ListWishlistedProducts( - TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin -): - template_name = "web/list_products.html" - queryset = Product.objects.filter(hidden=False).prefetch_related( - "images", - ) +class ListWishlistedProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): + template_name = 'web/list_products.html' + queryset = Product.objects.filter(hidden=False).prefetch_related('images') filter_class = ProductFilter def get_queryset(self): - product_ids = WishlistedProduct.objects.filter( - user=self.request.user - ).values_list("product", flat=True) + product_ids = WishlistedProduct.objects.filter(user=self.request.user).values_list('product', flat=True) return super().get_queryset().filter(pk__in=product_ids).distinct() def get_context_data(self, **kwargs): @@ -67,27 +53,21 @@ class ListWishlistedProducts( page = self.get_paginated_queryset(qs) return { - "current_page": page.number, - "num_pages": page.paginator.num_pages, - "page": page, - "has_next_page": page.has_next(), - "has_previous_page": page.has_previous(), + 'current_page': page.number, + 'num_pages': page.paginator.num_pages, + 'page': page, + 'has_next_page': page.has_next(), + 'has_previous_page': page.has_previous(), } def cart_dropdown(request, *args, **kwargs): cart, created = get_or_create_cart(request) - response = render( - request, - "components/cart/cart_navbar.html", - context={ - "cart": cart, - }, - ) + response = render(request, 'components/cart/cart_navbar.html', context={'cart': cart}) if not request.user.is_authenticated: - response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict") + response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite='strict') return response @@ -95,15 +75,13 @@ def cart(request, *args, **kwargs): cart, created = get_or_create_cart(request) items = CartItem.objects.filter(cart=cart) - base_total = Decimal("0.00") - tax_total = Decimal("0.00") + base_total = Decimal('0.00') + tax_total = Decimal('0.00') for item in items: price = ProductPrice.objects.filter(product=item.product, current=True).first() base_total += price.price * item.quantity - tax_total += round( - price.price * Decimal(price.tax.value / 100) * item.quantity, 2 - ) + tax_total += round(price.price * Decimal(price.tax.value / 100) * item.quantity, 2) total = base_total + tax_total @@ -112,8 +90,7 @@ def cart(request, *args, **kwargs): user=request.user, address_type=CustomerAddress.Types.BILLING, default=True ).first() shipping_address = CustomerAddress.objects.filter( - user=request.user, - address_type=CustomerAddress.Types.SHIPPING, + user=request.user, address_type=CustomerAddress.Types.SHIPPING ).first() else: billing_address = None @@ -123,62 +100,53 @@ def cart(request, *args, **kwargs): response = render( request, - "components/cart/cart.html", + 'components/cart/cart.html', context={ - "cart": cart, - "items": items, - "total": total, - "base_total": base_total, - "tax_total": tax_total, - "billing_address": billing_address, - "shipping_address": shipping_address, - "shipping_methods": shipping_methods, + 'cart': cart, + 'items': items, + 'total': total, + 'base_total': base_total, + 'tax_total': tax_total, + 'billing_address': billing_address, + 'shipping_address': shipping_address, + 'shipping_methods': shipping_methods, }, ) if not request.user.is_authenticated: - response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict") + response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite='strict') return response class WishlistButton(TemplateView): - template_name = "components/products/wishlist_button.html" + template_name = 'components/products/wishlist_button.html' def get_context_data(self, **kwargs): - product = get_object_or_404(Product, pk=self.kwargs.get("pk")) - is_wishlisted = WishlistedProduct.objects.filter( - user=self.request.user, product=product - ).exists() + product = get_object_or_404(Product, pk=self.kwargs.get('pk')) + is_wishlisted = WishlistedProduct.objects.filter(user=self.request.user, product=product).exists() - return { - "is_wishlisted": is_wishlisted, - "product": product, - } + return {'is_wishlisted': is_wishlisted, 'product': product} class UserInfoFormComponentView(HTMXFormComponent): form_class = UserInfoForm - hx_trigger = "updated_user_info" - save_button_text = _("Actualizar datos") + hx_trigger = 'updated_user_info' + save_button_text = _('Actualizar datos') def get_object(self) -> models.Model: return self.request.user def get_view_name(self): - return reverse("web:user_info_component") + return reverse('web:user_info_component') def get_initial_values(self, instance): - return { - "email": instance.email, - "first_name": instance.first_name, - "last_name": instance.last_name, - } + return {'email': instance.email, 'first_name': instance.first_name, 'last_name': instance.last_name} class ChangePasswordFormComponentView(HTMXFormComponent): form_class = ChangePasswordForm - hx_trigger = "changed_password" - save_button_text = _("Cambiar contraseña") + hx_trigger = 'changed_password' + save_button_text = _('Cambiar contraseña') def get_form(self, initial=True): if initial: @@ -190,16 +158,14 @@ class ChangePasswordFormComponentView(HTMXFormComponent): return self.request.user def get_view_name(self): - return reverse("web:change_password_component") + return reverse('web:change_password_component') class ListCustomerAddressComponent(TemplateView): - template_name = "components/users/retrieve_update_address.html" + template_name = 'components/users/retrieve_update_address.html' def get_context_data(self, **kwargs): - return { - "customer_addresses": CustomerAddress.objects.filter(user=self.request.user) - } + return {'customer_addresses': CustomerAddress.objects.filter(user=self.request.user)} def update_customer_address(request, pk): @@ -208,7 +174,7 @@ def update_customer_address(request, pk): if form.is_valid(): form.save() - return HttpResponse(status=200, headers={"HX-Trigger": "updated_addresses"}) + return HttpResponse(status=200, headers={'HX-Trigger': 'updated_addresses'}) return HttpResponse(form.errors, status=400) diff --git a/web/views/users.py b/web/views/users.py index 9a0241f..848a23a 100644 --- a/web/views/users.py +++ b/web/views/users.py @@ -6,11 +6,7 @@ from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ValidationError from django.db import IntegrityError from django.shortcuts import redirect, render, reverse -from django.utils.http import ( - url_has_allowed_host_and_scheme, - urlencode, - urlsafe_base64_decode, -) +from django.utils.http import url_has_allowed_host_and_scheme, urlencode, urlsafe_base64_decode from django.utils.text import gettext_lazy as _ from django.views.generic import TemplateView, View @@ -22,22 +18,22 @@ User = get_user_model() class RedirectionMixin: - redirect_to = "" + redirect_to = '' def get_redirection(self): return reverse(self.redirect_to) class LoginView(View, RedirectionMixin): - template_name = "users/login.html" - redirect_to = "web:index" + template_name = 'users/login.html' + redirect_to = 'web:index' login_form_class = LoginForm def get_redirection(self): - next_page = self.request.GET.get("next", "") + next_page = self.request.GET.get('next', '') if next_page: - return f"{reverse(self.redirect_to)}?next={next_page}" + return f'{reverse(self.redirect_to)}?next={next_page}' return reverse(self.redirect_to) @@ -54,11 +50,7 @@ class LoginView(View, RedirectionMixin): return render( request, self.template_name, - { - "form": login_form, - "title": web_settings.web_title, - "description": _("inicio de sesión"), - }, + {'form': login_form, 'title': web_settings.web_title, 'description': _('inicio de sesión')}, ) def post(self, request, *args, **kwargs): @@ -67,35 +59,25 @@ class LoginView(View, RedirectionMixin): if login_form.is_valid(): login_user(request, login_form.get_user()) - if not login_form.cleaned_data.get("remember_me"): + if not login_form.cleaned_data.get('remember_me'): request.session.set_expiry(0) return redirect(self.get_redirection()) return render( - request, - self.template_name, - { - "form": login_form, - "errors": login_form.errors.get("__all__"), - }, - status=400, + request, self.template_name, {'form': login_form, 'errors': login_form.errors.get('__all__')}, status=400 ) class RegisterView(TemplateView, RedirectionMixin): - template_name = "users/register.html" - redirect_to = "web:login" + template_name = 'users/register.html' + redirect_to = 'web:login' form_class = RegisterForm def get_context_data(self, **kwargs): settings = WebSettings.load() - return { - "form": self.form_class(), - "title": settings.web_title, - "description": _("crear cuenta"), - } + return {'form': self.form_class(), 'title': settings.web_title, 'description': _('crear cuenta')} def post(self, request, *args, **kwargs): settings = WebSettings.load() @@ -107,34 +89,27 @@ class RegisterView(TemplateView, RedirectionMixin): user = form.save() user.username = user.email - user.first_name = form.cleaned_data.get("first_name") - user.last_name = form.cleaned_data.get("last_name") + user.first_name = form.cleaned_data.get('first_name') + user.last_name = form.cleaned_data.get('last_name') user.save() login(request, user) return redirect(self.get_redirection()) else: errors = form.errors - except IntegrityError as e: - errors = [ - _("Ya existe un usuario con ese correo electrónico"), - ] + except IntegrityError: + errors = [_('Ya existe un usuario con ese correo electrónico')] return render( request, self.template_name, - { - "form": form, - "title": settings.web_title, - "description": _("Crear cuenta"), - "errors": errors, - }, + {'form': form, 'title': settings.web_title, 'description': _('Crear cuenta'), 'errors': errors}, status=400, ) class LogoutView(View, RedirectionMixin): - redirect_to = "web:index" + redirect_to = 'web:index' def get(self, request, *args, **kwargs): logout_user(request) @@ -146,31 +121,28 @@ class LogoutView(View, RedirectionMixin): class ResetPasswordView(TemplateView): - template_name = "users/reset_password.html" + template_name = 'users/reset_password.html' form_class = PasswordResetForm - redirect_to = "web:reset_password_email_sent" - html_reset_password_template_name = "mails/reset_password.html" - subject_template_name = "mails/subject_reset_password.txt" + redirect_to = 'web:reset_password_email_sent' + html_reset_password_template_name = 'mails/reset_password.html' + subject_template_name = 'mails/subject_reset_password.txt' def get_email_options(self): return { - "extra_email_context": {}, - "email_template_name": self.html_reset_password_template_name, - "html_email_template_name": self.html_reset_password_template_name, - "subject_template_name": self.subject_template_name, + 'extra_email_context': {}, + 'email_template_name': self.html_reset_password_template_name, + 'html_email_template_name': self.html_reset_password_template_name, + 'subject_template_name': self.subject_template_name, } def get_base_context(self): settings = WebSettings.load() - return { - "title": settings.web_title, - "description": _("Restablecer contraseña"), - } + return {'title': settings.web_title, 'description': _('Restablecer contraseña')} def get_context_data(self, **kwargs): context = self.get_base_context() - context.update({"form": self.form_class()}) + context.update({'form': self.form_class()}) return context def post(self, request, *args, **kwargs): @@ -178,9 +150,9 @@ class ResetPasswordView(TemplateView): if form.is_valid(): opts = { - "use_https": request.is_secure(), - "from_email": getattr(settings, "DEFAULT_FROM_EMAIL"), - "request": request, + 'use_https': request.is_secure(), + 'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'), + 'request': request, } opts.update(self.get_email_options()) @@ -188,41 +160,32 @@ class ResetPasswordView(TemplateView): return redirect(self.redirect_to) context = self.get_base_context() - context.update({"form": form}) + context.update({'form': form}) return render(request, self.template_name, context) class ResetPasswordEmailSent(TemplateView): - template_name = "users/reset_password_email_sent.html" + template_name = 'users/reset_password_email_sent.html' def get_context_data(self, **kwargs): settings = WebSettings.load() - return { - "title": settings.web_title, - "description": settings.web_description, - } + return {'title': settings.web_title, 'description': settings.web_description} class ResetPasswordConfirmView(TemplateView): - template_name = "users/reset_password_confirm.html" + template_name = 'users/reset_password_confirm.html' form_class = SetPasswordForm - redirect_to = "web:login" + redirect_to = 'web:login' token_generator = default_token_generator def get_user(self): try: - uidb64 = self.kwargs.get("uidb64") + uidb64 = self.kwargs.get('uidb64') uid = urlsafe_base64_decode(uidb64).decode() user = User.objects.get(pk=uid) - except ( - TypeError, - ValueError, - OverflowError, - User.DoesNotExist, - ValidationError, - ): + except (TypeError, ValueError, OverflowError, User.DoesNotExist, ValidationError): user = None return user @@ -235,15 +198,15 @@ class ResetPasswordConfirmView(TemplateView): new_token = self.generate_new_token_for_user(user) return { - "title": WebSettings.load().web_title, - "description": _("Establecer nueva contraseña"), - "form": self.form_class(user, {"token": new_token}), - "valid_token": valid_token, - "new_token": new_token, + 'title': WebSettings.load().web_title, + 'description': _('Establecer nueva contraseña'), + 'form': self.form_class(user, {'token': new_token}), + 'valid_token': valid_token, + 'new_token': new_token, } def check_token(self, user): - token = self.kwargs.get("token") + token = self.kwargs.get('token') valid_token = self.token_generator.check_token(user, token) return valid_token @@ -258,15 +221,15 @@ class ResetPasswordConfirmView(TemplateView): class MyAccountView(TemplateView): - template_name = "users/user_details.html" + template_name = 'users/user_details.html' def get_context_data(self, **kwargs): addresses = CustomerAddress.objects.filter(user=self.request.user) web_settings = WebSettings.load() return { - "title": f"{web_settings.web_title} - Mi cuenta", - "description": f"{web_settings.web_title} - Mi cuenta", - "customer_addresses": addresses, + 'title': f'{web_settings.web_title} - Mi cuenta', + 'description': f'{web_settings.web_title} - Mi cuenta', + 'customer_addresses': addresses, } diff --git a/web/views/web.py b/web/views/web.py index 6f91fe2..e11fe29 100644 --- a/web/views/web.py +++ b/web/views/web.py @@ -7,15 +7,7 @@ from django.utils.text import gettext_lazy as _ from django.views.decorators.http import require_http_methods from django.views.generic import CreateView, TemplateView -from shop.models import ( - CartItem, - Order, - OrderLine, - Product, - ProductCategory, - ShippingMethod, - WishlistedProduct, -) +from shop.models import CartItem, Order, OrderLine, Product, ProductCategory, ShippingMethod, WishlistedProduct from shop.redsys import RedsysClient from shop.utils import create_order_from_cart from web.forms import CreateOrderForm @@ -26,67 +18,64 @@ from web.utils import get_or_create_cart class IndexView(TemplateView): - template_name = "web/index.html" + template_name = 'web/index.html' def get_context_data(self, **kwargs): settings = WebSettings.load() - return { - "title": settings.web_title, - "description": settings.web_description, - } + return {'title': settings.web_title, 'description': settings.web_description} def get(self, request, *args, **kwargs): - if self.request.GET.get("next"): - return redirect(self.request.GET.get("next")) + if self.request.GET.get('next'): + return redirect(self.request.GET.get('next')) return super().get(request, *args, **kwargs) class CategoryView(TemplateView): - template_name = "web/index.html" + template_name = 'web/index.html' def get_context_data(self, **kwargs): settings = WebSettings.load() - slug = kwargs.get("slug") + slug = kwargs.get('slug') category = ProductCategory.objects.get(slug=slug) return { - "title": _(f"{settings.web_title} - {category.name}"), - "description": settings.web_description, - "category": category, - "filter_by_category": True, + 'title': _(f'{settings.web_title} - {category.name}'), + 'description': settings.web_description, + 'category': category, + 'filter_by_category': True, } class ProductDetail(TemplateView): - template_name = "web/product_detail.html" + template_name = 'web/product_detail.html' def get_context_data(self, pk, slug, **kwargs): settings = WebSettings.load() product = get_object_or_404(Product, pk=pk) return { - "product": product, - "title": f"{settings.web_title} - {product.name}", - "description": product.description, - "image": product.images.first(), + 'product': product, + 'title': f'{settings.web_title} - {product.name}', + 'description': product.description, + 'image': product.images.first(), } class CartDetail(TemplateView, CreateView): - template_name = "web/cart_detail.html" + template_name = 'web/cart_detail.html' form_class = CreateOrderForm def get_context_data(self, **kwargs): web_settings = WebSettings.load() cart, created = get_or_create_cart(self.request) - cart_items = CartItem.objects.filter(cart=cart).prefetch_related("product") + cart_items = CartItem.objects.filter(cart=cart).prefetch_related('product') return { - "title": _(f"{web_settings.web_title} - Carrito"), - "cart": cart, - "items": cart_items, - "description": _("resumen del carrito"), + 'title': _(f'{web_settings.web_title} - Carrito'), + 'cart': cart, + 'items': cart_items, + 'description': _('resumen del carrito'), } def post(self, request, *args, **kwargs): @@ -94,29 +83,29 @@ class CartDetail(TemplateView, CreateView): if not form.is_valid(): context = self.get_context_data(**kwargs) - context.update({"errors": form.errors}) + context.update({'errors': form.errors}) return self.render_to_response(context) - shipping_method_id = form.cleaned_data.get("shipping_method") + shipping_method_id = form.cleaned_data.get('shipping_method') cart, created = get_or_create_cart(request) - billing_address_full_name = form.cleaned_data.get("billing_address_full_name") - billing_address_address = form.cleaned_data.get("billing_address") - billing_address_town = form.cleaned_data.get("billing_address_town") - billing_address_state = form.cleaned_data.get("billing_address_state") - billing_address_country = form.cleaned_data.get("billing_address_country") - billing_address_zip = form.cleaned_data.get("billing_address_zip") + billing_address_full_name = form.cleaned_data.get('billing_address_full_name') + billing_address_address = form.cleaned_data.get('billing_address') + billing_address_town = form.cleaned_data.get('billing_address_town') + billing_address_state = form.cleaned_data.get('billing_address_state') + billing_address_country = form.cleaned_data.get('billing_address_country') + billing_address_zip = form.cleaned_data.get('billing_address_zip') - shipping_address_full_name = form.cleaned_data.get("shipping_address_full_name") - shipping_address_address = form.cleaned_data.get("shipping_address") - shipping_address_town = form.cleaned_data.get("shipping_address_town") - shipping_address_state = form.cleaned_data.get("shipping_address_state") - shipping_address_country = form.cleaned_data.get("shipping_address_country") - shipping_address_zip = form.cleaned_data.get("shipping_address_zip") - shipping_address_phone = form.cleaned_data.get("shipping_address_phone") + shipping_address_full_name = form.cleaned_data.get('shipping_address_full_name') + shipping_address_address = form.cleaned_data.get('shipping_address') + shipping_address_town = form.cleaned_data.get('shipping_address_town') + shipping_address_state = form.cleaned_data.get('shipping_address_state') + shipping_address_country = form.cleaned_data.get('shipping_address_country') + shipping_address_zip = form.cleaned_data.get('shipping_address_zip') + shipping_address_phone = form.cleaned_data.get('shipping_address_phone') - email = form.cleaned_data.get("email") + email = form.cleaned_data.get('email') shipping_method = get_object_or_404(ShippingMethod, pk=shipping_method_id) @@ -139,72 +128,62 @@ class CartDetail(TemplateView, CreateView): email=email, ) - return redirect(reverse("web:order", kwargs={"uuid": order.uuid})) + return redirect(reverse('web:order', kwargs={'uuid': order.uuid})) class OrderDetail(TemplateView): - template_name = "web/order.html" + template_name = 'web/order.html' def get_context_data(self, **kwargs): - order = get_object_or_404(Order, uuid=kwargs.get("uuid")) + order = get_object_or_404(Order, uuid=kwargs.get('uuid')) redsys_client = RedsysClient() parameters = redsys_client.get_body_for_order(order) - items = OrderLine.objects.select_related("product").filter( + items = OrderLine.objects.select_related('product').filter(order=order, product__is_shipping_method=False) + + shipping_cost = OrderLine.objects.select_related('product').filter( + order=order, product__is_shipping_method=True + ).aggregate(amount=Sum('total')).get('amount') or Decimal('0') + + base_total = OrderLine.objects.select_related('product').filter( order=order, product__is_shipping_method=False - ) + ).aggregate(amount=Sum('base_total')).get('amount') or Decimal('0') - shipping_cost = OrderLine.objects.select_related("product").filter( - order=order, - product__is_shipping_method=True, - ).aggregate(amount=Sum("total")).get("amount") or Decimal("0") - - base_total = OrderLine.objects.select_related("product").filter( - order=order, - product__is_shipping_method=False, - ).aggregate(amount=Sum("base_total")).get("amount") or Decimal("0") - - tax_total = OrderLine.objects.select_related("product").filter( - order=order, - product__is_shipping_method=False, - ).aggregate(amount=Sum("taxes")).get("amount") or Decimal("0") + tax_total = OrderLine.objects.select_related('product').filter( + order=order, product__is_shipping_method=False + ).aggregate(amount=Sum('taxes')).get('amount') or Decimal('0') settings = WebSettings.load() return { - "title": _(f"{settings.web_title} - Comprar"), - "description": _(f"{settings.web_title} - Comprar"), - "order": order, - "items": items, - "signature_version": parameters.get("Ds_SignatureVersion"), - "merchant_parameters": parameters.get("Ds_MerchantParameters"), - "signature": parameters.get("Ds_Signature"), - "redsys_target_url": redsys_client.get_target_url(), - "total": round(order.total, 2), - "base_total": round(base_total, 2), - "tax_total": round(tax_total, 2), - "shipping_cost": round(shipping_cost, 2), + 'title': _(f'{settings.web_title} - Comprar'), + 'description': _(f'{settings.web_title} - Comprar'), + 'order': order, + 'items': items, + 'signature_version': parameters.get('Ds_SignatureVersion'), + 'merchant_parameters': parameters.get('Ds_MerchantParameters'), + 'signature': parameters.get('Ds_Signature'), + 'redsys_target_url': redsys_client.get_target_url(), + 'total': round(order.total, 2), + 'base_total': round(base_total, 2), + 'tax_total': round(tax_total, 2), + 'shipping_cost': round(shipping_cost, 2), } class WishlistView(TemplateView): - template_name = "web/wishlist.html" + template_name = 'web/wishlist.html' def get_context_data(self, **kwargs): settings = WebSettings.load() - return { - "title": _(f"{settings.web_title} - Lista de deseados"), - "description": settings.web_description, - } + return {'title': _(f'{settings.web_title} - Lista de deseados'), 'description': settings.web_description} class OrdersView(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): - template_name = "web/orders.html" + template_name = 'web/orders.html' def get_queryset(self): - return Order.objects.filter(user=self.request.user).exclude( - status=Order.Statuses.STATUS_PENDING - ) + return Order.objects.filter(user=self.request.user).exclude(status=Order.Statuses.STATUS_PENDING) def get_context_data(self, **kwargs): settings = WebSettings.load() @@ -212,129 +191,86 @@ class OrdersView(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin): page = self.get_paginated_queryset(qs) return { - "title": _(f"{settings.web_title} - Mis pedidos"), - "page": page, - "has_next_page": page.has_next(), - "has_previous_page": page.has_previous(), + 'title': _(f'{settings.web_title} - Mis pedidos'), + 'page': page, + 'has_next_page': page.has_next(), + 'has_previous_page': page.has_previous(), } -@require_http_methods( - [ - "POST", - ] -) +@require_http_methods(['POST']) def add_cart_item(request, *args, **kwargs): - product = get_object_or_404(Product, pk=request.POST.get("product")) + product = get_object_or_404(Product, pk=request.POST.get('product')) cart, created = get_or_create_cart(request) - response = HttpResponse(status=201, headers={"HX-Trigger": "updated-cart"}) + response = HttpResponse(status=201, headers={'HX-Trigger': 'updated-cart'}) if created and request.user.is_anonymous: - response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite="strict") + response.set_cookie(ANONYMOUS_CART_ID_COOKIE_NAME, cart.uuid, samesite='strict') # Comprobamos si existe una línea de carrito para ese carrito de ese producto existing_cart_item = CartItem.objects.filter(cart=cart, product=product).first() # Si existe, simplemente le sumamos la cantidad a la línea ya existente if existing_cart_item is not None: - existing_cart_item.quantity += int(request.POST.get("quantity")) + existing_cart_item.quantity += int(request.POST.get('quantity')) existing_cart_item.save() # Si no, lo creamos else: - CartItem.objects.create( - cart=cart, - quantity=request.POST.get("quantity"), - product=product, - ) + CartItem.objects.create(cart=cart, quantity=request.POST.get('quantity'), product=product) return response -@require_http_methods( - [ - "POST", - ] -) +@require_http_methods(['POST']) def delete_cart_item(request, pk, *args, **kwargs): cart, created = get_or_create_cart(request) cart_item = get_object_or_404(CartItem, pk=pk, cart=cart) cart_item.delete() - return HttpResponse(status=204, headers={"HX-Trigger": "updated-cart"}) + return HttpResponse(status=204, headers={'HX-Trigger': 'updated-cart'}) -@require_http_methods( - [ - "POST", - ] -) +@require_http_methods(['POST']) def add_to_wishlist(request, *args, **kwargs): - product = get_object_or_404(Product, pk=request.POST.get("product")) - response = HttpResponse(status=201, headers={"HX-Trigger": "updated-wishlist"}) + product = get_object_or_404(Product, pk=request.POST.get('product')) + response = HttpResponse(status=201, headers={'HX-Trigger': 'updated-wishlist'}) if request.user.is_anonymous: return HttpResponse(status=400) - WishlistedProduct.objects.get_or_create( - user=request.user, - product=product, - ) + WishlistedProduct.objects.get_or_create(user=request.user, product=product) return response -@require_http_methods( - [ - "POST", - ] -) +@require_http_methods(['POST']) def delete_from_wishlist(request, pk, *args, **kwargs): - wishlisted_item = get_object_or_404( - WishlistedProduct, product_id=pk, user=request.user - ) + wishlisted_item = get_object_or_404(WishlistedProduct, product_id=pk, user=request.user) wishlisted_item.delete() - return HttpResponse(status=204, headers={"HX-Trigger": "updated-wishlist"}) + return HttpResponse(status=204, headers={'HX-Trigger': 'updated-wishlist'}) def manifest(request, *args, **kwargs): settings = WebSettings.load() data = { - "name": settings.web_title, - "short_name": settings.web_title, - "start_url": "/", - "background_color": settings.bg_color, - "theme_color": settings.theme_color, - "icons": [], - "display": "standalone", - "orientation": "portrait", + 'name': settings.web_title, + 'short_name': settings.web_title, + 'start_url': '/', + 'background_color': settings.bg_color, + 'theme_color': settings.theme_color, + 'icons': [], + 'display': 'standalone', + 'orientation': 'portrait', } if settings.logo: - data["icons"].append( - { - "src": settings.logo.url, - "sizes": "512x512", - "type": "image/png", - } - ) + data['icons'].append({'src': settings.logo.url, 'sizes': '512x512', 'type': 'image/png'}) if settings.logo_256: - data["icons"].append( - { - "src": settings.logo_256.url, - "sizes": "256x256", - "type": "image/png", - } - ) + data['icons'].append({'src': settings.logo_256.url, 'sizes': '256x256', 'type': 'image/png'}) if settings.logo_128: - data["icons"].append( - { - "src": settings.logo_128.url, - "sizes": "128x128", - "type": "image/png", - } - ) + data['icons'].append({'src': settings.logo_128.url, 'sizes': '128x128', 'type': 'image/png'}) return JsonResponse(data)