diff --git a/CHANGELOG.md b/CHANGELOG.md index e814d922..4c14ec71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ # Changelog ## Unrealeased changes -_Nothing yet_ + +- Adding Django 3 support +- Updating all dependencies to latest versions +- Dropping Python 2 compatibility +- Proforma API fields `archived_provider` and `archived_customer` now correctly return JSON objects rather than + stringified JSON objects + + ## 0.10.1 Fixed issue in autocomplete views where user.is_authenticated is no longer a function call and instead an attribute diff --git a/requirements/common.txt b/requirements/common.txt index 35521419..c0638caf 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -3,32 +3,31 @@ # UNMAINTAINED means the package owner is no longer maintaining it # Core -Django>=1.11,<2.3 # ----------------------------------------------------------- (bumped 2019-05-28) -sqlparse>=0.2,<0.3 # (required for some old migrations) ----------------------- (bumped 2018-06-07) +Django~=3.1.5 +sqlparse~=0.4.1 # (required for some old migrations) # Django Utils -django-fsm>=2.3,<2.7 # -------------------------------------------------------- (bumped 2018-06-07) -django-filter>=1.1,<2.1 # ----------------------------------------------------- (bumped 2019-01-31) -django-livefield>=2.8,<3.3 # -------------------------------------------------- (bumped 2019-06-10) -django-jsonfield==1.0.1 # ---------------------------------------------------- (checked 2018-06-07) -django-model-utils>=3.0,<3.2 # ------------------------------------------------ (bumped 2018-06-07) -django-annoying>=0.10,<0.11 # (various Django helpers) ------------------------ (bumped 2018-06-07) -django-autocomplete-light>=3.2,<3.3 # ----------------------------------------- (bumped 2018-06-07) +django-fsm~=2.7.1 +django-filter~=2.4.0 +django-livefield~=3.3.0 +django-model-utils~=4.1.1 +django-annoying~=0.10.6 +django-autocomplete-light~=3.8.1 # API -djangorestframework>=3.8,<3.10 # ---------------------------------------------- (bumped 2019-01-31) -djangorestframework-bulk<0.3 # ----------------------------------------------- (checked 2018-06-07) +djangorestframework~=3.12.2 +djangorestframework-bulk~=0.2.1 # I18n -pycountry>=16.11.08 # --------------------------------------------------------- (bumped 2018-06-07) -python-dateutil>=2.6,<2.8 # --------------------------------------------------- (bumped 2018-06-07) -pyvat>=1.3,<1.4 # ------------------------------------------------------------- (bumped 2018-06-07) +pycountry~=20.7.3 +python-dateutil~=2.8.1 +pyvat~=1.3.13 # Crypto -cryptography>=1.9,<2.4 # ------------------------------------------------------ (bumped 2018-11-02) -PyJWT>=1.5,<1.7 # ------------------------------------------------------------- (bumped 2018-06-07) +cryptography~=3.3.1 +PyJWT~=2.0.0 # Other -furl>=1,<1.3 # (URL parsing and manipulation) --------------------------------- (bumped 2018-08-10) -xhtml2pdf>=0.2,<0.3 # (PDF rendering, python-dev is required) ----------------- (bumped 2018-06-07) -PyPDF2>=1.26,<2 # (PDF manipulation) ------------------------------------------ (bumped 2018-06-07) +furl~=2.1.0 +xhtml2pdf~=0.2.5 +PyPDF2~=1.26.0 diff --git a/requirements/optionals.txt b/requirements/optionals.txt index 1c68544e..91c52ff3 100644 --- a/requirements/optionals.txt +++ b/requirements/optionals.txt @@ -1,3 +1,3 @@ -celery>=4.0,<4.2 # ------------------------------------------------------------ (bumped 2018-06-07) -redis>=2.10,<2.11 # ----------------------------------------------------------- (bumped 2018-06-07) -celery-once>=1.2,<2.1 # ------------------------------------------------------- (bumped 2018-06-07) +celery~=5.0.5 +redis~=3.5.3 +celery_once~=3.0.1 diff --git a/requirements/test.txt b/requirements/test.txt index d0152028..54b4ba51 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -6,8 +6,8 @@ flake8==2.4.1 freezegun==0.3.8 coverage==3.7.1 django-coverage==1.2.4 -pytest==3.6.4 -pytest-django==3.4.5 +pytest==5.4.0 +pytest-django==4.1.0 factory-boy==2.5.2 pep8==1.7.0 Faker==0.7.17 diff --git a/settings.py b/settings.py index 1d2c4961..5cf4d4b4 100644 --- a/settings.py +++ b/settings.py @@ -67,7 +67,7 @@ INSTALLED_APPS = EXTERNAL_APPS + INTERNAL_APPS -ROOT_URLCONF = 'silver.urls' +ROOT_URLCONF = 'silver.urls_project' PROJECT_ROOT = os.path.dirname(__file__) FIXTURE_DIRS = ( @@ -92,7 +92,8 @@ "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", - "django.contrib.messages.context_processors.messages" + "django.template.context_processors.request", + "django.contrib.messages.context_processors.messages", ) } } diff --git a/silver/admin.py b/silver/admin.py index def271cb..34c07b7b 100644 --- a/silver/admin.py +++ b/silver/admin.py @@ -43,10 +43,10 @@ from django.shortcuts import render from django.urls import reverse from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.html import escape, conditional_escape from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from silver.documents_generator import DocumentsGenerator from silver.models import ( @@ -284,7 +284,7 @@ def perform_action(self, request, action, queryset): user_id=request.user.id, content_type_id=ContentType.objects.get_for_model(entry).pk, object_id=entry.id, - object_repr=force_text(entry), + object_repr=force_str(entry), action_flag=CHANGE, change_message='{action} action initiated by user.'.format( action=action.replace('_', ' ').strip().capitalize() @@ -778,7 +778,7 @@ def _call_method_on_queryset(self, request, method, queryset, action): user_id=request.user.id, content_type_id=ContentType.objects.get_for_model(document).pk, object_id=document.id, - object_repr=force_text(document), + object_repr=force_str(document), action_flag=CHANGE, change_message='{action} action initiated by user.'.format( action=action.replace('_', ' ').strip().capitalize() @@ -788,7 +788,7 @@ def _call_method_on_queryset(self, request, method, queryset, action): results[document]['result'] = mark_safe(error) results[document]['success'] = False except ValueError as error: - results[document]['result'] = force_text(error) + results[document]['result'] = force_str(error) results[document]['success'] = False except AttributeError: results[document]['success'] = False diff --git a/silver/api/serializers/billing_entities_serializers.py b/silver/api/serializers/billing_entities_serializers.py index f04039a4..ea98d05c 100644 --- a/silver/api/serializers/billing_entities_serializers.py +++ b/silver/api/serializers/billing_entities_serializers.py @@ -14,6 +14,7 @@ from __future__ import absolute_import +from django.core.serializers.json import DjangoJSONEncoder from rest_framework import serializers from rest_framework.fields import JSONField from rest_framework.relations import HyperlinkedRelatedField @@ -85,7 +86,7 @@ class CustomerSerializer(serializers.HyperlinkedModelSerializer): view_name='transaction-list', source='*', lookup_url_kwarg='customer_pk' ) - meta = JSONField(required=False) + meta = JSONField(required=False, encoder=DjangoJSONEncoder) url = CustomerUrl(view_name='customer-detail', read_only=True, source='*') class Meta: diff --git a/silver/api/serializers/documents_serializers.py b/silver/api/serializers/documents_serializers.py index 87f9b571..3494a575 100644 --- a/silver/api/serializers/documents_serializers.py +++ b/silver/api/serializers/documents_serializers.py @@ -14,6 +14,7 @@ from __future__ import absolute_import +from django.core.serializers.json import DjangoJSONEncoder from rest_framework import serializers from rest_framework.fields import JSONField, DecimalField @@ -117,8 +118,8 @@ class InvoiceSerializer(AutoCleanSerializerMixin, customer = CustomerUrl(view_name='customer-detail', queryset=Customer.objects.all()) transactions = TransactionSerializer(many=True, read_only=True) - archived_customer = JSONField(read_only=True) - archived_provider = JSONField(read_only=True) + archived_customer = JSONField(read_only=True, encoder=DjangoJSONEncoder) + archived_provider = JSONField(read_only=True, encoder=DjangoJSONEncoder) total_in_transaction_currency = serializers.DecimalField( max_digits=None, decimal_places=2, coerce_to_string=True, read_only=True, ) diff --git a/silver/api/serializers/subscriptions_serializers.py b/silver/api/serializers/subscriptions_serializers.py index dcd4030c..fed3f017 100644 --- a/silver/api/serializers/subscriptions_serializers.py +++ b/silver/api/serializers/subscriptions_serializers.py @@ -14,6 +14,7 @@ from __future__ import absolute_import +from django.core.serializers.json import DjangoJSONEncoder from rest_framework import serializers from rest_framework.fields import JSONField from rest_framework.reverse import reverse @@ -67,7 +68,7 @@ class SubscriptionSerializer(serializers.HyperlinkedModelSerializer): url = SubscriptionUrl(view_name='subscription-detail', source='*', queryset=Subscription.objects.all(), required=False) updateable_buckets = serializers.ReadOnlyField() - meta = JSONField(required=False) + meta = JSONField(required=False, encoder=DjangoJSONEncoder) class Meta: model = Subscription diff --git a/silver/api/serializers/transaction_serializers.py b/silver/api/serializers/transaction_serializers.py index 4a6bdfe1..62bbe0c3 100644 --- a/silver/api/serializers/transaction_serializers.py +++ b/silver/api/serializers/transaction_serializers.py @@ -50,7 +50,8 @@ def get_url(self, obj, view_name, request, format): def get_object(self, view_name, view_args, view_kwargs): try: transaction_uuid = jwt.decode(view_kwargs['token'], - settings.PAYMENT_METHOD_SECRET)['transaction'] + settings.PAYMENT_METHOD_SECRET, + algorithms=['HS256'])['transaction'] return self.queryset.get(uuid=transaction_uuid) except (jwt.ExpiredSignatureError, jwt.DecodeError, jwt.InvalidTokenError): return None diff --git a/silver/api/urls.py b/silver/api/urls.py index 1f396e1b..afa7db2c 100644 --- a/silver/api/urls.py +++ b/silver/api/urls.py @@ -14,7 +14,7 @@ from __future__ import absolute_import -from django.conf.urls import url +from django.conf.urls import re_path from silver import views as silver_views from silver.api.views import billing_entities_views, documents_views, payment_method_views, \ @@ -22,97 +22,97 @@ urlpatterns = [ - url(r'^customers/$', - billing_entities_views.CustomerList.as_view(), name='customer-list'), - url(r'^customers/(?P[0-9]+)/$', - billing_entities_views.CustomerDetail.as_view(), name='customer-detail'), + re_path(r'^customers/$', + billing_entities_views.CustomerList.as_view(), name='customer-list'), + re_path(r'^customers/(?P[0-9]+)/$', + billing_entities_views.CustomerDetail.as_view(), name='customer-detail'), - url(r'^customers/(?P[0-9]+)/subscriptions/$', - subscription_views.SubscriptionList.as_view(), name='subscription-list'), - url(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/$', - subscription_views.SubscriptionDetail.as_view(), name='subscription-detail'), - url(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/metered-features/(?P([^/])+)/$', - subscription_views.MeteredFeatureUnitsLogDetail.as_view(), name='mf-log-units'), - url(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/activate/$', - subscription_views.SubscriptionActivate.as_view(), name='sub-activate'), - url(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/cancel/$', - subscription_views.SubscriptionCancel.as_view(), name='sub-cancel'), - url(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/reactivate/$', - subscription_views.SubscriptionReactivate.as_view(), name='sub-reactivate'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/$', + subscription_views.SubscriptionList.as_view(), name='subscription-list'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/$', + subscription_views.SubscriptionDetail.as_view(), name='subscription-detail'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/metered-features/(?P([^/])+)/$', + subscription_views.MeteredFeatureUnitsLogDetail.as_view(), name='mf-log-units'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/activate/$', + subscription_views.SubscriptionActivate.as_view(), name='sub-activate'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/cancel/$', + subscription_views.SubscriptionCancel.as_view(), name='sub-cancel'), + re_path(r'^customers/(?P[0-9]+)/subscriptions/(?P[0-9]+)/reactivate/$', + subscription_views.SubscriptionReactivate.as_view(), name='sub-reactivate'), - url(r'^customers/(?P[0-9]+)/payment_methods/$', - payment_method_views.PaymentMethodList.as_view(), name='payment-method-list'), - url(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/$', - payment_method_views.PaymentMethodDetail.as_view(), name='payment-method-detail'), - url(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/(?P(cancel))_request/$', - payment_method_views.PaymentMethodAction.as_view(), name='payment-method-action'), + re_path(r'^customers/(?P[0-9]+)/payment_methods/$', + payment_method_views.PaymentMethodList.as_view(), name='payment-method-list'), + re_path(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/$', + payment_method_views.PaymentMethodDetail.as_view(), name='payment-method-detail'), + re_path(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/(?P(cancel))_request/$', + payment_method_views.PaymentMethodAction.as_view(), name='payment-method-action'), - url(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/transactions/$', - transaction_views.TransactionList.as_view(), name='payment-method-transaction-list'), - url(r'^customers/(?P[0-9]+)/transactions/$', - transaction_views.TransactionList.as_view(), name='transaction-list'), - url(r'^customers/(?P[0-9]+)/transactions/(?P[0-9a-z-]+)/$', - transaction_views.TransactionDetail.as_view(), name='transaction-detail'), - url(r'^customers/(?P[0-9]+)/transactions/(?P[0-9a-z-]+)/(?P(cancel))_request/$', - transaction_views.TransactionAction.as_view(), name='transaction-action'), + re_path(r'^customers/(?P[0-9]+)/payment_methods/(?P[0-9]+)/transactions/$', + transaction_views.TransactionList.as_view(), name='payment-method-transaction-list'), + re_path(r'^customers/(?P[0-9]+)/transactions/$', + transaction_views.TransactionList.as_view(), name='transaction-list'), + re_path(r'^customers/(?P[0-9]+)/transactions/(?P[0-9a-z-]+)/$', + transaction_views.TransactionDetail.as_view(), name='transaction-detail'), + re_path(r'^customers/(?P[0-9]+)/transactions/(?P[0-9a-z-]+)/(?P(cancel))_request/$', + transaction_views.TransactionAction.as_view(), name='transaction-action'), - url(r'^payment_processors/$', - payment_method_views.PaymentProcessorList.as_view(), name='payment-processor-list'), - url(r'^payment_processors/(?P[a-zA-Z\-\_]+)/$', - payment_method_views.PaymentProcessorDetail.as_view(), name='payment-processor-detail'), + re_path(r'^payment_processors/$', + payment_method_views.PaymentProcessorList.as_view(), name='payment-processor-list'), + re_path(r'^payment_processors/(?P[a-zA-Z\-\_]+)/$', + payment_method_views.PaymentProcessorDetail.as_view(), name='payment-processor-detail'), - url(r'^plans/$', - plan_views.PlanList.as_view(), name='plan-list'), - url(r'^plans/(?P[0-9]+)/$', - plan_views.PlanDetail.as_view(), name='plan-detail'), - url(r'plans/(?P[0-9]+)/metered-features/$', - plan_views.PlanMeteredFeatures.as_view(), name='plans-metered-features'), + re_path(r'^plans/$', + plan_views.PlanList.as_view(), name='plan-list'), + re_path(r'^plans/(?P[0-9]+)/$', + plan_views.PlanDetail.as_view(), name='plan-detail'), + re_path(r'plans/(?P[0-9]+)/metered-features/$', + plan_views.PlanMeteredFeatures.as_view(), name='plans-metered-features'), - url(r'^metered-features/$', - subscription_views.MeteredFeatureList.as_view(), name='metered-feature-list'), + re_path(r'^metered-features/$', + subscription_views.MeteredFeatureList.as_view(), name='metered-feature-list'), - url(r'^providers/$', - billing_entities_views.ProviderListCreate.as_view(), name='provider-list'), - url(r'^providers/(?P[0-9]+)/$', - billing_entities_views.ProviderRetrieveUpdateDestroy.as_view(), name='provider-detail'), + re_path(r'^providers/$', + billing_entities_views.ProviderListCreate.as_view(), name='provider-list'), + re_path(r'^providers/(?P[0-9]+)/$', + billing_entities_views.ProviderRetrieveUpdateDestroy.as_view(), name='provider-detail'), - url(r'^product-codes/$', - product_code_views.ProductCodeListCreate.as_view(), name='productcode-list'), - url(r'^product-codes/(?P[0-9]+)/$', - product_code_views.ProductCodeRetrieveUpdate.as_view(), name='productcode-detail'), + re_path(r'^product-codes/$', + product_code_views.ProductCodeListCreate.as_view(), name='productcode-list'), + re_path(r'^product-codes/(?P[0-9]+)/$', + product_code_views.ProductCodeRetrieveUpdate.as_view(), name='productcode-detail'), - url(r'^invoices/$', - documents_views.InvoiceListCreate.as_view(), name='invoice-list'), - url(r'^invoices/(?P[0-9]+)/$', - documents_views.InvoiceRetrieveUpdate.as_view(), name='invoice-detail'), - url(r'^invoices/(?P[0-9]+)/entries/$', - documents_views.InvoiceEntryCreate.as_view(), name='invoice-entry-create'), - url(r'^invoices/(?P[0-9]+)/entries/(?P[0-9]+)/$', - documents_views.InvoiceEntryUpdateDestroy.as_view(), name='invoice-entry-update'), - url(r'^invoices/(?P[0-9]+)/state/$', - documents_views.InvoiceStateHandler.as_view(), name='invoice-state'), - url(r'^invoices/(?P\d+).pdf$', - silver_views.invoice_pdf, name='invoice-pdf'), + re_path(r'^invoices/$', + documents_views.InvoiceListCreate.as_view(), name='invoice-list'), + re_path(r'^invoices/(?P[0-9]+)/$', + documents_views.InvoiceRetrieveUpdate.as_view(), name='invoice-detail'), + re_path(r'^invoices/(?P[0-9]+)/entries/$', + documents_views.InvoiceEntryCreate.as_view(), name='invoice-entry-create'), + re_path(r'^invoices/(?P[0-9]+)/entries/(?P[0-9]+)/$', + documents_views.InvoiceEntryUpdateDestroy.as_view(), name='invoice-entry-update'), + re_path(r'^invoices/(?P[0-9]+)/state/$', + documents_views.InvoiceStateHandler.as_view(), name='invoice-state'), + re_path(r'^invoices/(?P\d+).pdf$', + silver_views.invoice_pdf, name='invoice-pdf'), - url(r'^proformas/$', - documents_views.ProformaListCreate.as_view(), name='proforma-list'), - url(r'^proformas/(?P[0-9]+)/$', - documents_views.ProformaRetrieveUpdate.as_view(), name='proforma-detail'), - url(r'^proformas/(?P[0-9]+)/entries/$', - documents_views.ProformaEntryCreate.as_view(), name='proforma-entry-create'), - url(r'^proformas/(?P[0-9]+)/entries/(?P[0-9]+)/$', - documents_views.ProformaEntryUpdateDestroy.as_view(), - name='proforma-entry-update'), - url(r'^proformas/(?P[0-9]+)/state/$', - documents_views.ProformaStateHandler.as_view(), name='proforma-state'), - url(r'^proformas/(?P[0-9]+)/invoice/$', - documents_views.ProformaInvoiceRetrieveCreate.as_view(), - name='proforma-invoice'), - url(r'^proformas/(?P\d+).pdf$', - silver_views.proforma_pdf, name='proforma-pdf'), - url(r'^pdfs/(?P[0-9]+)/$', - documents_views.PDFRetrieve.as_view(), - name='pdf'), - url(r'^documents/$', - documents_views.DocumentList.as_view(), name='document-list') + re_path(r'^proformas/$', + documents_views.ProformaListCreate.as_view(), name='proforma-list'), + re_path(r'^proformas/(?P[0-9]+)/$', + documents_views.ProformaRetrieveUpdate.as_view(), name='proforma-detail'), + re_path(r'^proformas/(?P[0-9]+)/entries/$', + documents_views.ProformaEntryCreate.as_view(), name='proforma-entry-create'), + re_path(r'^proformas/(?P[0-9]+)/entries/(?P[0-9]+)/$', + documents_views.ProformaEntryUpdateDestroy.as_view(), + name='proforma-entry-update'), + re_path(r'^proformas/(?P[0-9]+)/state/$', + documents_views.ProformaStateHandler.as_view(), name='proforma-state'), + re_path(r'^proformas/(?P[0-9]+)/invoice/$', + documents_views.ProformaInvoiceRetrieveCreate.as_view(), + name='proforma-invoice'), + re_path(r'^proformas/(?P\d+).pdf$', + silver_views.proforma_pdf, name='proforma-pdf'), + re_path(r'^pdfs/(?P[0-9]+)/$', + documents_views.PDFRetrieve.as_view(), + name='pdf'), + re_path(r'^documents/$', + documents_views.DocumentList.as_view(), name='document-list') ] diff --git a/silver/api/views/billing_entities_views.py b/silver/api/views/billing_entities_views.py index 4c9b547c..1984b45a 100644 --- a/silver/api/views/billing_entities_views.py +++ b/silver/api/views/billing_entities_views.py @@ -32,7 +32,7 @@ class CustomerList(generics.ListCreateAPIView): serializer_class = CustomerSerializer queryset = Customer.objects.all() filter_backends = (DjangoFilterBackend,) - filter_class = CustomerFilter + filterset_class = CustomerFilter class CustomerDetail(generics.RetrieveUpdateDestroyAPIView): @@ -53,7 +53,7 @@ class ProviderListCreate(ListBulkCreateAPIView): serializer_class = ProviderSerializer queryset = Provider.objects.all() filter_backends = (DjangoFilterBackend,) - filter_class = ProviderFilter + filterset_class = ProviderFilter class ProviderRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView): diff --git a/silver/api/views/documents_views.py b/silver/api/views/documents_views.py index c9e710f1..1d311599 100644 --- a/silver/api/views/documents_views.py +++ b/silver/api/views/documents_views.py @@ -40,7 +40,7 @@ class InvoiceListCreate(generics.ListCreateAPIView): .select_related('related_document')\ .prefetch_related('invoice_transactions') filter_backends = (DjangoFilterBackend,) - filter_class = InvoiceFilter + filterset_class = InvoiceFilter class InvoiceRetrieveUpdate(generics.RetrieveUpdateAPIView): @@ -231,7 +231,7 @@ class ProformaListCreate(generics.ListCreateAPIView): .select_related('related_document')\ .prefetch_related('proforma_transactions') filter_backends = (DjangoFilterBackend,) - filter_class = ProformaFilter + filterset_class = ProformaFilter class ProformaRetrieveUpdate(generics.RetrieveUpdateAPIView): @@ -362,7 +362,7 @@ def put(self, request, *args, **kwargs): class DocumentList(ListAPIView): permission_classes = (permissions.IsAuthenticated,) serializer_class = DocumentSerializer - filter_class = BillingDocumentFilter + filterset_class = BillingDocumentFilter filter_backends = (filters.OrderingFilter, DjangoFilterBackend) ordering_fields = ('due_date', ) ordering = ('-due_date', '-number') diff --git a/silver/api/views/payment_method_views.py b/silver/api/views/payment_method_views.py index a07a8ce0..2d3dc970 100644 --- a/silver/api/views/payment_method_views.py +++ b/silver/api/views/payment_method_views.py @@ -56,7 +56,7 @@ class PaymentMethodList(ListCreateAPIView): permission_classes = (permissions.IsAuthenticated,) serializer_class = PaymentMethodSerializer filter_backends = (DjangoFilterBackend,) - filter_class = PaymentMethodFilter + filterset_class = PaymentMethodFilter def get_queryset(self): return PaymentMethod.objects.filter(customer=self.customer) diff --git a/silver/api/views/plan_views.py b/silver/api/views/plan_views.py index 95614ca8..cacb457b 100644 --- a/silver/api/views/plan_views.py +++ b/silver/api/views/plan_views.py @@ -33,7 +33,7 @@ class PlanList(generics.ListCreateAPIView): serializer_class = PlanSerializer queryset = Plan.objects.all().prefetch_related('metered_features') filter_backends = (DjangoFilterBackend,) - filter_class = PlanFilter + filterset_class = PlanFilter class PlanDetail(generics.RetrieveDestroyAPIView): diff --git a/silver/api/views/subscription_views.py b/silver/api/views/subscription_views.py index 9a6ec9f8..e6dd7369 100644 --- a/silver/api/views/subscription_views.py +++ b/silver/api/views/subscription_views.py @@ -23,7 +23,7 @@ from django_filters.rest_framework import DjangoFilterBackend from django.utils import timezone -from django.utils.encoding import force_text +from django.utils.encoding import force_str from rest_framework import generics, permissions, status from rest_framework.generics import get_object_or_404 @@ -46,7 +46,7 @@ class MeteredFeatureList(generics.ListCreateAPIView): serializer_class = MeteredFeatureSerializer queryset = MeteredFeature.objects.all() filter_backends = (DjangoFilterBackend,) - filter_class = MeteredFeaturesFilter + filterset_class = MeteredFeaturesFilter class MeteredFeatureDetail(generics.RetrieveAPIView): @@ -63,7 +63,7 @@ class SubscriptionList(generics.ListCreateAPIView): permission_classes = (permissions.IsAuthenticated,) serializer_class = SubscriptionSerializer filter_backends = (DjangoFilterBackend,) - filter_class = SubscriptionFilter + filterset_class = SubscriptionFilter def get_queryset(self): customer_pk = self.kwargs.get('customer_pk', None) @@ -74,7 +74,7 @@ def post(self, request, *args, **kwargs): customer_pk = self.kwargs.get('customer_pk', None) url = reverse('customer-detail', kwargs={'customer_pk': customer_pk}, request=request) - request.data.update({force_text('customer'): force_text(url)}) + request.data.update({force_str('customer'): force_str(url)}) return super(SubscriptionList, self).post(request, *args, **kwargs) diff --git a/silver/api/views/transaction_views.py b/silver/api/views/transaction_views.py index ed5a8e48..b0ec5d03 100644 --- a/silver/api/views/transaction_views.py +++ b/silver/api/views/transaction_views.py @@ -35,7 +35,7 @@ class TransactionList(ListCreateAPIView): permission_classes = (permissions.IsAuthenticated,) serializer_class = TransactionSerializer filter_backends = (DjangoFilterBackend,) - filter_class = TransactionFilter + filterset_class = TransactionFilter def get_queryset(self): customer_pk = self.kwargs.get('customer_pk', None) diff --git a/silver/fixtures/factories.py b/silver/fixtures/factories.py index fd835833..66bbf2d7 100644 --- a/silver/fixtures/factories.py +++ b/silver/fixtures/factories.py @@ -22,7 +22,6 @@ from django.contrib.auth import get_user_model from django.utils import timezone -from django.utils.six import text_type from silver.models import (Provider, Plan, MeteredFeature, Customer, Subscription, Invoice, ProductCode, PDF, @@ -255,8 +254,8 @@ class DocumentEntryFactory(factory.django.DjangoModelFactory): class Meta: model = DocumentEntry - description = factory.Sequence(lambda n: text_type('Description{cnt}').format(cnt=n)) - unit = factory.Sequence(lambda n: text_type('Unit{cnt}').format(cnt=n)) + description = factory.Sequence(lambda n: 'Description{cnt}'.format(cnt=n)) + unit = factory.Sequence(lambda n: 'Unit{cnt}'.format(cnt=n)) quantity = factory.fuzzy.FuzzyDecimal(low=1.00, high=50000.00, precision=4) unit_price = factory.fuzzy.FuzzyDecimal(low=0.01, high=100.00, precision=4) product_code = factory.SubFactory(ProductCodeFactory) diff --git a/silver/migrations/0001_initial.py b/silver/migrations/0001_initial.py index a5ecc561..d4c9ea9e 100644 --- a/silver/migrations/0001_initial.py +++ b/silver/migrations/0001_initial.py @@ -19,7 +19,6 @@ from django.db import models, migrations import django.db.models.deletion import django_fsm -import jsonfield.fields import livefield.fields import silver.models import django.core.validators @@ -112,9 +111,9 @@ class Migration(migrations.Migration): serialize=False, auto_created=True, primary_key=True)), ('number', models.IntegerField(null=True, blank=True)), ('archived_customer', - jsonfield.fields.JSONField(default=dict)), + models.JSONField(default=dict)), ('archived_provider', - jsonfield.fields.JSONField(default=dict)), + models.JSONField(default=dict)), ('due_date', models.DateField(null=True, blank=True)), ('issue_date', models.DateField(null=True, blank=True)), ('paid_date', models.DateField(null=True, blank=True)), @@ -212,9 +211,9 @@ class Migration(migrations.Migration): serialize=False, auto_created=True, primary_key=True)), ('number', models.IntegerField(null=True, blank=True)), ('archived_customer', - jsonfield.fields.JSONField(default=dict)), + models.JSONField(default=dict)), ('archived_provider', - jsonfield.fields.JSONField(default=dict)), + models.JSONField(default=dict)), ('due_date', models.DateField(null=True, blank=True)), ('issue_date', models.DateField(null=True, blank=True)), ('paid_date', models.DateField(null=True, blank=True)), diff --git a/silver/migrations/0003_auto_20150417_0634.py b/silver/migrations/0003_auto_20150417_0634.py index d824d2db..ca24b3aa 100644 --- a/silver/migrations/0003_auto_20150417_0634.py +++ b/silver/migrations/0003_auto_20150417_0634.py @@ -16,8 +16,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations -import jsonfield.fields +from django.db import migrations, models class Migration(migrations.Migration): @@ -30,16 +29,16 @@ class Migration(migrations.Migration): migrations.AddField( model_name='customer', name='meta', - field=jsonfield.fields.JSONField(null=True, blank=True), + field=models.JSONField(null=True, blank=True), ), migrations.AddField( model_name='provider', name='meta', - field=jsonfield.fields.JSONField(null=True, blank=True), + field=models.JSONField(null=True, blank=True), ), migrations.AddField( model_name='subscription', name='meta', - field=jsonfield.fields.JSONField(null=True, blank=True), + field=models.JSONField(null=True, blank=True), ), ] diff --git a/silver/migrations/0032_auto_20170201_1342.py b/silver/migrations/0032_auto_20170201_1342.py index 8a821d10..de886d53 100644 --- a/silver/migrations/0032_auto_20170201_1342.py +++ b/silver/migrations/0032_auto_20170201_1342.py @@ -4,7 +4,6 @@ from django.db import migrations, models import django.db.models.deletion import django_fsm -import jsonfield.fields from decimal import Decimal import silver.utils.models import django.utils.timezone @@ -64,7 +63,7 @@ def customer_name_split_reverse(apps, schema_editor): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('payment_processor', models.CharField(max_length=256, choices=[(b'manual', b'manual')])), ('added_at', models.DateTimeField(default=django.utils.timezone.now)), - ('data', jsonfield.fields.JSONField(default={}, null=True, blank=True)), + ('data', models.JSONField(default={}, null=True, blank=True)), ('verified', models.BooleanField(default=False)), ('canceled', models.BooleanField(default=False)), ], @@ -76,7 +75,7 @@ def customer_name_split_reverse(apps, schema_editor): ('amount', models.DecimalField(max_digits=12, decimal_places=2, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))])), ('currency', models.CharField(help_text=b'The currency used for billing.', max_length=4, choices=[('AED', 'UAE Dirham'), ('AFN', 'Afghani'), ('ALL', 'Lek'), ('AMD', 'Armenian Dram'), ('ANG', 'Netherlands Antillean Guilder'), ('AOA', 'Kwanza'), ('ARS', 'Argentine Peso'), ('AUD', 'Australian Dollar'), ('AWG', 'Aruban Florin'), ('AZN', 'Azerbaijanian Manat'), ('BAM', 'Convertible Mark'), ('BBD', 'Barbados Dollar'), ('BDT', 'Taka'), ('BGN', 'Bulgarian Lev'), ('BHD', 'Bahraini Dinar'), ('BIF', 'Burundi Franc'), ('BMD', 'Bermudian Dollar'), ('BND', 'Brunei Dollar'), ('BOB', 'Boliviano'), ('BRL', 'Brazilian Real'), ('BSD', 'Bahamian Dollar'), ('BTN', 'Ngultrum'), ('BWP', 'Pula'), ('BYR', 'Belarusian Ruble'), ('BZD', 'Belize Dollar'), ('CAD', 'Canadian Dollar'), ('CDF', 'Congolese Franc'), ('CHF', 'Swiss Franc'), ('CLP', 'Chilean Peso'), ('CNY', 'Yuan Renminbi'), ('COP', 'Colombian Peso'), ('CRC', 'Costa Rican Colon'), ('CUC', 'Peso Convertible'), ('CUP', 'Cuban Peso'), ('CVE', 'Cabo Verde Escudo'), ('CZK', 'Czech Koruna'), ('DJF', 'Djibouti Franc'), ('DKK', 'Danish Krone'), ('DOP', 'Dominican Peso'), ('DZD', 'Algerian Dinar'), ('EGP', 'Egyptian Pound'), ('ERN', 'Nakfa'), ('ETB', 'Ethiopian Birr'), ('EUR', 'Euro'), ('FJD', 'Fiji Dollar'), ('FKP', 'Falkland Islands Pound'), ('GBP', 'Pound Sterling'), ('GEL', 'Lari'), ('GHS', 'Ghana Cedi'), ('GIP', 'Gibraltar Pound'), ('GMD', 'Dalasi'), ('GNF', 'Guinea Franc'), ('GTQ', 'Quetzal'), ('GYD', 'Guyana Dollar'), ('HKD', 'Hong Kong Dollar'), ('HNL', 'Lempira'), ('HRK', 'Kuna'), ('HTG', 'Gourde'), ('HUF', 'Forint'), ('IDR', 'Rupiah'), ('ILS', 'New Israeli Sheqel'), ('INR', 'Indian Rupee'), ('IQD', 'Iraqi Dinar'), ('IRR', 'Iranian Rial'), ('ISK', 'Iceland Krona'), ('JMD', 'Jamaican Dollar'), ('JOD', 'Jordanian Dinar'), ('JPY', 'Yen'), ('KES', 'Kenyan Shilling'), ('KGS', 'Som'), ('KHR', 'Riel'), ('KMF', 'Comoro Franc'), ('KPW', 'North Korean Won'), ('KRW', 'Won'), ('KWD', 'Kuwaiti Dinar'), ('KYD', 'Cayman Islands Dollar'), ('KZT', 'Tenge'), ('LAK', 'Kip'), ('LBP', 'Lebanese Pound'), ('LKR', 'Sri Lanka Rupee'), ('LRD', 'Liberian Dollar'), ('LSL', 'Loti'), ('LYD', 'Libyan Dinar'), ('MAD', 'Moroccan Dirham'), ('MDL', 'Moldovan Leu'), ('MGA', 'Malagasy Ariary'), ('MKD', 'Denar'), ('MMK', 'Kyat'), ('MNT', 'Tugrik'), ('MOP', 'Pataca'), ('MRO', 'Ouguiya'), ('MUR', 'Mauritius Rupee'), ('MVR', 'Rufiyaa'), ('MWK', 'Malawi Kwacha'), ('MXN', 'Mexican Peso'), ('MYR', 'Malaysian Ringgit'), ('MZN', 'Mozambique Metical'), ('NAD', 'Namibia Dollar'), ('NGN', 'Naira'), ('NIO', 'Cordoba Oro'), ('NOK', 'Norwegian Krone'), ('NPR', 'Nepalese Rupee'), ('NZD', 'New Zealand Dollar'), ('OMR', 'Rial Omani'), ('PAB', 'Balboa'), ('PEN', 'Sol'), ('PGK', 'Kina'), ('PHP', 'Philippine Peso'), ('PKR', 'Pakistan Rupee'), ('PLN', 'Zloty'), ('PYG', 'Guarani'), ('QAR', 'Qatari Rial'), ('RON', 'Romanian Leu'), ('RSD', 'Serbian Dinar'), ('RUB', 'Russian Ruble'), ('RWF', 'Rwanda Franc'), ('SAR', 'Saudi Riyal'), ('SBD', 'Solomon Islands Dollar'), ('SCR', 'Seychelles Rupee'), ('SDG', 'Sudanese Pound'), ('SEK', 'Swedish Krona'), ('SGD', 'Singapore Dollar'), ('SHP', 'Saint Helena Pound'), ('SLL', 'Leone'), ('SOS', 'Somali Shilling'), ('SRD', 'Surinam Dollar'), ('SSP', 'South Sudanese Pound'), ('STD', 'Dobra'), ('SVC', 'El Salvador Colon'), ('SYP', 'Syrian Pound'), ('SZL', 'Lilangeni'), ('THB', 'Baht'), ('TJS', 'Somoni'), ('TMT', 'Turkmenistan New Manat'), ('TND', 'Tunisian Dinar'), ('TOP', 'Pa\u2019anga'), ('TRY', 'Turkish Lira'), ('TTD', 'Trinidad and Tobago Dollar'), ('TWD', 'New Taiwan Dollar'), ('TZS', 'Tanzanian Shilling'), ('UAH', 'Hryvnia'), ('UGX', 'Uganda Shilling'), ('USD', 'US Dollar'), ('UYU', 'Peso Uruguayo'), ('UZS', 'Uzbekistan Sum'), ('VEF', 'Bol\xedvar'), ('VND', 'Dong'), ('VUV', 'Vatu'), ('WST', 'Tala'), ('XAF', 'CFA Franc BEAC'), ('XAG', 'Silver'), ('XAU', 'Gold'), ('XBA', 'Bond Markets Unit European Composite Unit (EURCO)'), ('XBB', 'Bond Markets Unit European Monetary Unit (E.M.U.-6)'), ('XBC', 'Bond Markets Unit European Unit of Account 9 (E.U.A.-9)'), ('XBD', 'Bond Markets Unit European Unit of Account 17 (E.U.A.-17)'), ('XCD', 'East Caribbean Dollar'), ('XDR', 'SDR (Special Drawing Right)'), ('XOF', 'CFA Franc BCEAO'), ('XPD', 'Palladium'), ('XPF', 'CFP Franc'), ('XPT', 'Platinum'), ('XSU', 'Sucre'), ('XTS', 'Codes specifically reserved for testing purposes'), ('XUA', 'ADB Unit of Account'), ('XXX', 'The codes assigned for transactions where no currency is involved'), ('YER', 'Yemeni Rial'), ('ZAR', 'Rand'), ('ZMW', 'Zambian Kwacha'), ('ZWL', 'Zimbabwe Dollar')])), ('external_reference', models.CharField(max_length=256, null=True, blank=True)), - ('data', jsonfield.fields.JSONField(default={}, null=True, blank=True)), + ('data', models.JSONField(default={}, null=True, blank=True)), ('state', django_fsm.FSMField(default=b'initial', max_length=8, choices=[(b'canceled', 'Canceled'), (b'refunded', 'Refunded'), (b'initial', 'Initial'), (b'failed', 'Failed'), (b'settled', 'Settled'), (b'pending', 'Pending')])), ('uuid', models.UUIDField(default=uuid.uuid4)), ('valid_until', models.DateTimeField(null=True, blank=True)), @@ -134,7 +133,7 @@ def customer_name_split_reverse(apps, schema_editor): migrations.AlterField( model_name='customer', name='meta', - field=jsonfield.fields.JSONField(default={}, null=True, blank=True), + field=models.JSONField(default={}, null=True, blank=True), ), migrations.AlterField( model_name='invoice', @@ -184,7 +183,7 @@ def customer_name_split_reverse(apps, schema_editor): migrations.AlterField( model_name='provider', name='meta', - field=jsonfield.fields.JSONField(default={}, null=True, blank=True), + field=models.JSONField(default={}, null=True, blank=True), ), migrations.AlterField( model_name='provider', diff --git a/silver/migrations/0037_auto_20170719_1159.py b/silver/migrations/0037_auto_20170719_1159.py index 406a837d..ef0cf639 100644 --- a/silver/migrations/0037_auto_20170719_1159.py +++ b/silver/migrations/0037_auto_20170719_1159.py @@ -31,17 +31,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customer', name='meta', - field=annoying.fields.JSONField(blank=True, default={}, null=True), + field=models.JSONField(blank=True, default={}, null=True), ), migrations.AlterField( model_name='invoice', name='archived_customer', - field=annoying.fields.JSONField(blank=True, default=dict, null=True), + field=models.JSONField(blank=True, default=dict, null=True), ), migrations.AlterField( model_name='invoice', name='archived_provider', - field=annoying.fields.JSONField(blank=True, default=dict, null=True), + field=models.JSONField(blank=True, default=dict, null=True), ), migrations.AlterField( model_name='invoice', @@ -56,7 +56,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='paymentmethod', name='data', - field=annoying.fields.JSONField(blank=True, default={}, null=True), + field=models.JSONField(blank=True, default={}, null=True), ), migrations.AlterField( model_name='plan', @@ -66,12 +66,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='proforma', name='archived_customer', - field=annoying.fields.JSONField(blank=True, default=dict, null=True), + field=models.JSONField(blank=True, default=dict, null=True), ), migrations.AlterField( model_name='proforma', name='archived_provider', - field=annoying.fields.JSONField(blank=True, default=dict, null=True), + field=models.JSONField(blank=True, default=dict, null=True), ), migrations.AlterField( model_name='proforma', @@ -91,12 +91,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='provider', name='meta', - field=annoying.fields.JSONField(blank=True, default={}, null=True), + field=models.JSONField(blank=True, default={}, null=True), ), migrations.AlterField( model_name='subscription', name='meta', - field=annoying.fields.JSONField(blank=True, null=True), + field=models.JSONField(blank=True, null=True), ), migrations.AlterField( model_name='transaction', @@ -106,6 +106,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='transaction', name='data', - field=annoying.fields.JSONField(blank=True, default={}, null=True), + field=models.JSONField(blank=True, default={}, null=True), ), ] diff --git a/silver/migrations/0043_auto_20171113_1048.py b/silver/migrations/0043_auto_20171113_1048.py index 6c235ff2..b0d9dc20 100644 --- a/silver/migrations/0043_auto_20171113_1048.py +++ b/silver/migrations/0043_auto_20171113_1048.py @@ -90,8 +90,8 @@ class Migration(migrations.Migration): ('kind', models.CharField(db_index=True, max_length=8, verbose_name=silver.models.documents.base.get_billing_documents_kinds)), ('series', models.CharField(blank=True, db_index=True, max_length=20, null=True)), ('number', models.IntegerField(blank=True, db_index=True, null=True)), - ('archived_customer', annoying.fields.JSONField(blank=True, default=dict, null=True)), - ('archived_provider', annoying.fields.JSONField(blank=True, default=dict, null=True)), + ('archived_customer', models.JSONField(blank=True, default=dict, null=True)), + ('archived_provider', models.JSONField(blank=True, default=dict, null=True)), ('due_date', models.DateField(blank=True, null=True)), ('issue_date', models.DateField(blank=True, db_index=True, null=True)), ('paid_date', models.DateField(blank=True, null=True)), diff --git a/silver/migrations/0047_auto_20180507_1319.py b/silver/migrations/0047_auto_20180507_1319.py index 77d22329..0abc7c99 100644 --- a/silver/migrations/0047_auto_20180507_1319.py +++ b/silver/migrations/0047_auto_20180507_1319.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='subscription', name='meta', - field=annoying.fields.JSONField(blank=True, default={}, null=True), + field=models.JSONField(blank=True, default={}, null=True), ), ] diff --git a/silver/migrations/0054_auto_20210109_1153.py b/silver/migrations/0054_auto_20210109_1153.py new file mode 100644 index 00000000..8c50884e --- /dev/null +++ b/silver/migrations/0054_auto_20210109_1153.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.5 on 2021-01-09 11:53 + +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + + dependencies = [ + ('silver', '0053_auto_20191028_1254'), + ] + + operations = [ + migrations.AlterField( + model_name='transaction', + name='fail_code', + field=models.CharField(blank=True, choices=[('default', 'default'), ('insufficient_funds', 'insufficient_funds'), ('expired_payment_method', 'expired_payment_method'), ('expired_card', 'expired_card'), ('invalid_payment_method', 'invalid_payment_method'), ('invalid_card', 'invalid_card'), ('limit_exceeded', 'limit_exceeded'), ('transaction_declined', 'transaction_declined'), ('transaction_declined_by_bank', 'transaction_declined_by_bank'), ('transaction_hard_declined', 'transaction_hard_declined'), ('transaction_hard_declined_by_bank', 'transaction_hard_declined_by_bank')], max_length=64, null=True), + ), + ] diff --git a/silver/migrations/0055_auto_20210109_1200.py b/silver/migrations/0055_auto_20210109_1200.py new file mode 100644 index 00000000..e4c81a11 --- /dev/null +++ b/silver/migrations/0055_auto_20210109_1200.py @@ -0,0 +1,53 @@ +# Generated by Django 3.1.5 on 2021-01-09 12:00 + +import django.core.serializers.json +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + + dependencies = [ + ('silver', '0054_auto_20210109_1153'), + ] + + operations = [ + migrations.DeleteModel( + name='Document', + ), + migrations.AlterField( + model_name='billingdocumentbase', + name='archived_customer', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True), + ), + migrations.AlterField( + model_name='billingdocumentbase', + name='archived_provider', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True), + ), + migrations.AlterField( + model_name='customer', + name='meta', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True), + ), + migrations.AlterField( + model_name='paymentmethod', + name='data', + field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True), + ), + migrations.AlterField( + model_name='plan', + name='generate_documents_on_trial_end', + field=models.BooleanField(help_text='If this is set to True, then billing documents will be generated when the subscription trial ends, instead of waiting for the end of the billing cycle.', null=True), + ), + migrations.AlterField( + model_name='plan', + name='prebill_plan', + field=models.BooleanField(help_text='If this is set to True, then the plan base amount will be billed at thebeginning of the billing cycle rather than after the end.', null=True), + ), + migrations.AlterField( + model_name='plan', + name='separate_cycles_during_trial', + field=models.BooleanField(help_text='If this is set to True, then the trial period cycle will be split if it spans across multiple billing intervals.', null=True), + ), + ] diff --git a/silver/models/billing_entities/base.py b/silver/models/billing_entities/base.py index f0e2713c..5ac9fed7 100644 --- a/silver/models/billing_entities/base.py +++ b/silver/models/billing_entities/base.py @@ -14,13 +14,13 @@ from __future__ import absolute_import, unicode_literals -from annoying.fields import JSONField +from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import JSONField from livefield import LiveModel from django.conf import settings from django.db import models from django.utils.text import slugify -from django.utils.encoding import python_2_unicode_compatible from silver.utils.international import countries @@ -28,7 +28,6 @@ PAYMENT_DUE_DAYS = getattr(settings, 'SILVER_DEFAULT_DUE_DAYS', 5) -@python_2_unicode_compatible class BaseBillingEntity(LiveModel): company = models.CharField(max_length=128, blank=True, null=True) address_1 = models.CharField(max_length=128) @@ -44,7 +43,7 @@ class BaseBillingEntity(LiveModel): help_text='Extra information to display on the invoice ' '(markdown formatted).' ) - meta = JSONField(blank=True, null=True, default={}) + meta = JSONField(blank=True, null=True, default=dict, encoder=DjangoJSONEncoder) class Meta: abstract = True diff --git a/silver/models/billing_entities/provider.py b/silver/models/billing_entities/provider.py index 476e3f72..1973f753 100644 --- a/silver/models/billing_entities/provider.py +++ b/silver/models/billing_entities/provider.py @@ -24,7 +24,7 @@ from django.dispatch import receiver from django.urls import reverse from django.utils.html import escape -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from silver.models.billing_entities.base import BaseBillingEntity diff --git a/silver/models/documents/base.py b/silver/models/documents/base.py index 88f19344..88185ce9 100644 --- a/silver/models/documents/base.py +++ b/silver/models/documents/base.py @@ -18,7 +18,8 @@ from datetime import datetime, timedelta from decimal import Decimal -from annoying.fields import JSONField +from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import JSONField from django_fsm import FSMField, transition, TransitionNotAllowed, post_transition from model_utils import Choices @@ -34,9 +35,9 @@ from django.db.models import Max, ForeignKey, F from django.template.loader import select_template from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible, force_text +from django.utils.encoding import force_str from django.utils.text import slugify -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.utils.module_loading import import_string from silver.currencies import CurrencyConverter, RateNotFound @@ -59,7 +60,7 @@ def documents_pdf_path(document, filename): path = '{prefix}{company}/{doc_name}/{date}/{filename}'.format( - company=slugify(force_text( + company=slugify(force_str( document.provider.company or document.provider.name)), date=document.issue_date.strftime('%Y/%m'), doc_name=('%ss' % document.__class__.__name__).lower(), @@ -106,7 +107,6 @@ def get_billing_documents_kinds(): for subclass in BillingDocumentBase.__subclasses__()) -@python_2_unicode_compatible class BillingDocumentBase(models.Model): objects = BillingDocumentManager.from_queryset(BillingDocumentQuerySet)() @@ -132,8 +132,8 @@ class STATES(object): number = models.IntegerField(blank=True, null=True, db_index=True) customer = models.ForeignKey('Customer', on_delete=models.CASCADE) provider = models.ForeignKey('Provider', on_delete=models.CASCADE) - archived_customer = JSONField(default=dict, null=True, blank=True) - archived_provider = JSONField(default=dict, null=True, blank=True) + archived_customer = JSONField(default=dict, null=True, blank=True, encoder=DjangoJSONEncoder) + archived_provider = JSONField(default=dict, null=True, blank=True, encoder=DjangoJSONEncoder) due_date = models.DateField(null=True, blank=True) issue_date = models.DateField(null=True, blank=True, db_index=True) paid_date = models.DateField(null=True, blank=True) diff --git a/silver/models/documents/entries.py b/silver/models/documents/entries.py index 8689555c..88306163 100644 --- a/silver/models/documents/entries.py +++ b/silver/models/documents/entries.py @@ -18,12 +18,10 @@ from django.core.validators import MinValueValidator from django.db import models -from django.utils.encoding import python_2_unicode_compatible from silver.utils.decorators import require_transaction_currency_and_xe_rate -@python_2_unicode_compatible class DocumentEntry(models.Model): description = models.CharField(max_length=1024) unit = models.CharField(max_length=1024, blank=True, null=True) diff --git a/silver/models/payment_methods.py b/silver/models/payment_methods.py index 5262e4ec..3427057d 100644 --- a/silver/models/payment_methods.py +++ b/silver/models/payment_methods.py @@ -16,9 +16,10 @@ from itertools import chain -from annoying.fields import JSONField from annoying.functions import get_object_or_None from cryptography.fernet import InvalidToken, Fernet +from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import JSONField from django_fsm import TransitionNotAllowed from model_utils.managers import InheritanceManager @@ -28,7 +29,6 @@ from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from silver import payment_processors from silver.models import Invoice, Proforma @@ -40,7 +40,6 @@ class PaymentMethodInvalid(Exception): pass -@python_2_unicode_compatible class PaymentMethod(models.Model): class PaymentProcessors: @classmethod @@ -56,7 +55,7 @@ def as_list(cls): blank=False, null=False, max_length=256) customer = models.ForeignKey(Customer, models.CASCADE) added_at = models.DateTimeField(default=timezone.now) - data = JSONField(blank=True, null=True, default={}) + data = JSONField(blank=True, null=True, default=dict, encoder=DjangoJSONEncoder) verified = models.BooleanField(default=False) canceled = models.BooleanField(default=False) diff --git a/silver/models/plans.py b/silver/models/plans.py index ab4a6284..1d333c27 100644 --- a/silver/models/plans.py +++ b/silver/models/plans.py @@ -19,8 +19,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from django.db import models -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from silver.utils.international import currencies from silver.utils.models import UnsavedForeignKey @@ -31,7 +30,6 @@ def get_queryset(self): return super(PlanManager, self).get_queryset().select_related('product_code') -@python_2_unicode_compatible class Plan(models.Model): objects = PlanManager() @@ -74,15 +72,18 @@ class INTERVALS(object): 'customer to this plan.', verbose_name='Trial days' ) - generate_documents_on_trial_end = models.NullBooleanField( + generate_documents_on_trial_end = models.BooleanField( + null=True, help_text="If this is set to True, then billing documents will be generated when the " "subscription trial ends, instead of waiting for the end of the billing cycle." ) - separate_cycles_during_trial = models.NullBooleanField( + separate_cycles_during_trial = models.BooleanField( + null=True, help_text="If this is set to True, then the trial period cycle will be split if it spans " "across multiple billing intervals." ) - prebill_plan = models.NullBooleanField( + prebill_plan = models.BooleanField( + null=True, help_text="If this is set to True, then the plan base amount will be billed at the" "beginning of the billing cycle rather than after the end." ) @@ -138,7 +139,6 @@ def provider_flow(self): return self.provider.flow -@python_2_unicode_compatible class MeteredFeature(models.Model): name = models.CharField( max_length=200, diff --git a/silver/models/product_codes.py b/silver/models/product_codes.py index ba395443..38bd2ee5 100644 --- a/silver/models/product_codes.py +++ b/silver/models/product_codes.py @@ -15,10 +15,8 @@ from __future__ import absolute_import, unicode_literals from django.db import models -from django.utils.encoding import python_2_unicode_compatible -@python_2_unicode_compatible class ProductCode(models.Model): value = models.CharField(max_length=128, unique=True) diff --git a/silver/models/subscriptions.py b/silver/models/subscriptions.py index c91b4855..4afb2f5f 100644 --- a/silver/models/subscriptions.py +++ b/silver/models/subscriptions.py @@ -21,9 +21,10 @@ from decimal import Decimal from functools import reduce -from annoying.fields import JSONField from annoying.functions import get_object_or_None from dateutil import rrule +from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import JSONField from django_fsm import FSMField, transition, TransitionNotAllowed from model_utils import Choices @@ -36,9 +37,8 @@ from django.template import TemplateDoesNotExist from django.template.loader import get_template, render_to_string from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible from django.utils.timezone import utc -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from silver.models.billing_entities import Customer from silver.models.documents import DocumentEntry @@ -61,7 +61,6 @@ def field_template_path(field, provider=None): return 'billing_documents/{field}.html'.format(field=field) -@python_2_unicode_compatible class MeteredFeatureUnitsLog(models.Model): metered_feature = models.ForeignKey('MeteredFeature', related_name='consumed', on_delete=models.CASCADE) @@ -121,7 +120,6 @@ def __str__(self): return self.metered_feature.name -@python_2_unicode_compatible class Subscription(models.Model): class STATES(object): ACTIVE = 'active' @@ -181,7 +179,7 @@ class CANCEL_OPTIONS(object): choices=STATE_CHOICES, max_length=12, default=STATES.INACTIVE, help_text='The state the subscription is in.' ) - meta = JSONField(blank=True, null=True, default={}) + meta = JSONField(blank=True, null=True, default=dict, encoder=DjangoJSONEncoder) def clean(self): errors = dict() @@ -1015,7 +1013,6 @@ def __str__(self): return u'%s (%s)' % (self.customer, self.plan.name) -@python_2_unicode_compatible class BillingLog(models.Model): subscription = models.ForeignKey('Subscription', on_delete=models.CASCADE, related_name='billing_logs') diff --git a/silver/models/transactions/transaction.py b/silver/models/transactions/transaction.py index f97b5a37..3395f8ee 100644 --- a/silver/models/transactions/transaction.py +++ b/silver/models/transactions/transaction.py @@ -19,19 +19,19 @@ from decimal import Decimal -from annoying.fields import JSONField from annoying.functions import get_object_or_None +from django.core.serializers.json import DjangoJSONEncoder from django_fsm import FSMField, post_transition, transition from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from django.db import models, transaction -from django.db.models import Q +from django.db.models import Q, JSONField from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone -from django.utils.encoding import python_2_unicode_compatible, force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_str +from django.utils.translation import gettext_lazy as _ from silver.models import Invoice, Proforma from silver.models.transactions.codes import FAIL_CODES, REFUND_CODES, CANCEL_CODES @@ -41,7 +41,6 @@ logger = logging.getLogger(__name__) -@python_2_unicode_compatible class Transaction(AutoCleanModelMixin, models.Model): _provider = None @@ -78,7 +77,7 @@ def as_choices(cls): ) external_reference = models.CharField(max_length=256, null=True, blank=True) - data = JSONField(default={}, null=True, blank=True) + data = JSONField(default=dict, null=True, blank=True, encoder=DjangoJSONEncoder) state = FSMField(max_length=8, choices=States.as_choices(), default=States.Initial) @@ -96,7 +95,7 @@ def as_choices(cls): updated_at = AutoDateTimeField(default=timezone.now) fail_code = models.CharField( - choices=[(code, code) for code in FAIL_CODES.keys()], max_length=32, + choices=[(code, code) for code in FAIL_CODES.keys()], max_length=64, null=True, blank=True ) refund_code = models.CharField( @@ -288,7 +287,7 @@ def update_document_state(self): self.document.pay() def __str__(self): - return force_text(self.uuid) + return force_str(self.uuid) @receiver(post_transition) diff --git a/silver/payment_processors/base.py b/silver/payment_processors/base.py index 911a6296..ff6b366b 100644 --- a/silver/payment_processors/base.py +++ b/silver/payment_processors/base.py @@ -17,7 +17,7 @@ from django.conf import settings from django.template.loader import select_template from django.utils.deconstruct import deconstructible -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.utils.module_loading import import_string from django.utils.text import slugify @@ -107,7 +107,7 @@ def __repr__(self): return self.name def __str__(self): - return force_text(self.name) + return force_str(self.name) def __eq__(self, other): return self.__class__ is other.__class__ diff --git a/silver/templates/billing_documents/document_pdf_base.html b/silver/templates/billing_documents/document_pdf_base.html index 1be542e6..5e48f801 100644 --- a/silver/templates/billing_documents/document_pdf_base.html +++ b/silver/templates/billing_documents/document_pdf_base.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} diff --git a/silver/templates/forms/transaction_form.html b/silver/templates/forms/transaction_form.html index 865c2e88..ec5219ec 100644 --- a/silver/templates/forms/transaction_form.html +++ b/silver/templates/forms/transaction_form.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} Payment for {{ document.kind }} {{ document.series }}-{{ document.number }} diff --git a/silver/templates/transactions/complete_payment.html b/silver/templates/transactions/complete_payment.html index 923a8baa..9e4bb71f 100644 --- a/silver/templates/transactions/complete_payment.html +++ b/silver/templates/transactions/complete_payment.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} diff --git a/silver/templates/transactions/expired_payment.html b/silver/templates/transactions/expired_payment.html index ccdc8ded..6e325c6d 100644 --- a/silver/templates/transactions/expired_payment.html +++ b/silver/templates/transactions/expired_payment.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} <html> <head> <title> diff --git a/silver/tests/admin/test_invoice.py b/silver/tests/admin/test_invoice.py index 49c2f54d..ddefd878 100644 --- a/silver/tests/admin/test_invoice.py +++ b/silver/tests/admin/test_invoice.py @@ -24,7 +24,7 @@ from django.contrib.contenttypes.models import ContentType from django.urls import reverse from django.test import TestCase, Client -from django.utils.encoding import force_text +from django.utils.encoding import force_str from silver.fixtures.factories import InvoiceFactory @@ -77,7 +77,7 @@ def test_actions_log_entries(self): user_id=self.user.pk, content_type_id=ContentType.objects.get_for_model(invoice).pk, object_id=invoice.pk, - object_repr=force_text(invoice), + object_repr=force_str(invoice), action_flag=CHANGE, change_message='{action} action initiated by user.'.format( action=action.capitalize().replace('_', ' ') diff --git a/silver/tests/admin/test_proforma.py b/silver/tests/admin/test_proforma.py index ae9ff9b8..9381349e 100644 --- a/silver/tests/admin/test_proforma.py +++ b/silver/tests/admin/test_proforma.py @@ -21,7 +21,7 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.urls import reverse -from django.utils.encoding import force_text +from django.utils.encoding import force_str from django.test import TestCase, Client from django_fsm import TransitionNotAllowed @@ -76,7 +76,7 @@ def test_actions_log_entries(self): user_id=self.user.pk, content_type_id=ContentType.objects.get_for_model(proforma).pk, object_id=proforma.pk, - object_repr=force_text(proforma), + object_repr=force_str(proforma), action_flag=CHANGE, change_message='{action} action initiated by user.'.format( action=action.capitalize().replace('_', ' ') diff --git a/silver/tests/api/specs/customer.py b/silver/tests/api/specs/customer.py index bca1ece5..1f74756b 100644 --- a/silver/tests/api/specs/customer.py +++ b/silver/tests/api/specs/customer.py @@ -1,27 +1,21 @@ -from decimal import Decimal - -from django.utils.six import text_type - -from silver.tests.api.specs.utils import text_type_or_none - def spec_archived_customer(customer): return { - 'company': text_type(customer.company), - 'email': text_type(customer.email), - 'address_1': text_type(customer.address_1), - 'address_2': text_type(customer.address_2), - 'city': text_type(customer.city), - 'country': text_type(customer.country), - 'state': text_type(customer.state), - 'zip_code': text_type(customer.zip_code), - 'extra': text_type(customer.extra), + 'company': customer.company, + 'email': customer.email, + 'address_1': customer.address_1, + 'address_2': customer.address_2, + 'city': customer.city, + 'country': customer.country, + 'state': customer.state, + 'zip_code': customer.zip_code, + 'extra': customer.extra, 'meta': customer.meta, - 'first_name': text_type(customer.first_name), - 'last_name': text_type(customer.last_name), - 'customer_reference': text_type(customer.customer_reference), + 'first_name': customer.first_name, + 'last_name': customer.last_name, + 'customer_reference': customer.customer_reference, 'consolidated_billing': bool(customer.consolidated_billing), 'payment_due_days': int(customer.payment_due_days), - 'sales_tax_number': text_type_or_none(customer.sales_tax_number), + 'sales_tax_number': customer.sales_tax_number, 'sales_tax_percent': "%.2f" % customer.sales_tax_percent } diff --git a/silver/tests/api/specs/document_entry.py b/silver/tests/api/specs/document_entry.py index 1a582504..9f7fd522 100644 --- a/silver/tests/api/specs/document_entry.py +++ b/silver/tests/api/specs/document_entry.py @@ -1,7 +1,6 @@ from datetime import date from decimal import Decimal -from django.utils.six import text_type from silver.tests.api.specs.utils import ResourceDefinition @@ -17,20 +16,20 @@ }, 'description': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda entry: entry.description }, 'unit': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda entry: entry.unit, }, 'unit_price': { - 'expected_input_types': (int, float, text_type), + 'expected_input_types': (int, float, str), 'output': lambda entry: "%.4f" % Decimal(entry.unit_price) }, 'quantity': { - 'expected_input_types': (int, float, text_type), + 'expected_input_types': (int, float, str), 'output': lambda entry: "%.4f" % Decimal(entry.quantity) }, 'total_before_tax': { @@ -60,7 +59,7 @@ }, 'product_code': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda entry: entry.product_code, } }) diff --git a/silver/tests/api/specs/invoice.py b/silver/tests/api/specs/invoice.py index 86cb8ea3..4e6c81d6 100644 --- a/silver/tests/api/specs/invoice.py +++ b/silver/tests/api/specs/invoice.py @@ -2,7 +2,6 @@ from decimal import Decimal from django.conf import settings -from django.utils.six import text_type from silver.tests.api.specs.customer import spec_archived_customer from silver.tests.api.specs.document_entry import spec_document_entry, document_entry_definition @@ -22,7 +21,7 @@ }, 'proforma': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: ( spec_proforma_url(invoice.related_document) if invoice.related_document else None ), @@ -47,14 +46,14 @@ ), }, 'customer': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: spec_customer_url(invoice.customer), 'assertions': [ lambda input, invoice, output: input == output ] }, 'provider': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: spec_provider_url(invoice.provider), 'assertions': [ lambda input, invoice, output: input == output @@ -62,7 +61,7 @@ }, 'series': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: invoice.provider.invoice_series, 'assertions': [ lambda input, invoice, output: input == output if input else True @@ -77,7 +76,7 @@ ] }, 'currency': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: invoice.currency, 'assertions': [ lambda input, invoice, output: input == output @@ -85,7 +84,7 @@ }, 'transaction_currency': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: invoice.transaction_currency or invoice.currency, 'assertions': [ lambda input, invoice, output: input == output if input else True @@ -105,7 +104,7 @@ }, 'transaction_xe_rate': { 'required': False, - 'expected_input_types': (int, float, text_type), + 'expected_input_types': (int, float, str), 'output': lambda invoice: ( "%.4f" % invoice.transaction_xe_rate if invoice.transaction_xe_rate else None ), @@ -119,7 +118,7 @@ }, 'state': { 'read_only': True, - 'output': lambda invoice: text_type(invoice.state), + 'output': lambda invoice: invoice.state, 'assertions': [ lambda output, **kw: output in ['draft', 'issued', 'canceled', 'paid'] ] @@ -205,7 +204,7 @@ }, 'sales_tax_percent': { 'required': False, - 'expected_input_types': (int, float, text_type), + 'expected_input_types': (int, float, str), 'output': lambda invoice: "%.2f" % invoice.sales_tax_percent, 'assertions': [ lambda input, invoice, output: ( @@ -216,7 +215,7 @@ }, 'sales_tax_name': { 'required': False, - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda invoice: invoice.sales_tax_name, 'assertions': [ lambda input, invoice, output: input == output if input else output == 'VAT' diff --git a/silver/tests/api/specs/provider.py b/silver/tests/api/specs/provider.py index 155e765a..e2c4a235 100644 --- a/silver/tests/api/specs/provider.py +++ b/silver/tests/api/specs/provider.py @@ -1,19 +1,18 @@ -from django.utils.six import text_type def spec_archived_provider(provider): return { - 'company': text_type(provider.company), - 'email': text_type(provider.email), - 'address_1': text_type(provider.address_1), - 'address_2': text_type(provider.address_2), - 'city': text_type(provider.city), - 'country': text_type(provider.country), - 'state': text_type(provider.state), - 'zip_code': text_type(provider.zip_code), - 'extra': text_type(provider.extra), + 'company': provider.company, + 'email': provider.email, + 'address_1': provider.address_1, + 'address_2': provider.address_2, + 'city': provider.city, + 'country': provider.country, + 'state': provider.state, + 'zip_code': provider.zip_code, + 'extra': provider.extra, 'meta': provider.meta, - 'name': text_type(provider.name), - 'invoice_series': text_type(provider.invoice_series), - 'proforma_series': text_type(provider.proforma_series), + 'name': provider.name, + 'invoice_series': provider.invoice_series, + 'proforma_series': provider.proforma_series, } diff --git a/silver/tests/api/specs/transaction.py b/silver/tests/api/specs/transaction.py index 3b97e88e..236621dd 100644 --- a/silver/tests/api/specs/transaction.py +++ b/silver/tests/api/specs/transaction.py @@ -1,7 +1,6 @@ from decimal import Decimal from django.utils import timezone -from django.utils.six import text_type from silver.tests.api.specs.url import ( spec_invoice_url, spec_customer_url, spec_provider_url, spec_proforma_url, spec_transaction_url, @@ -16,33 +15,33 @@ transaction_definition = ResourceDefinition("transaction", { 'id': { 'read_only': True, - 'output': lambda transaction: text_type(transaction.uuid), + 'output': lambda transaction: transaction.uuid, }, 'url': { 'read_only': True, 'output': lambda transaction: spec_transaction_url(transaction), }, 'customer': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: spec_customer_url(transaction.customer), 'assertions': [ lambda input, transaction, output: input == output ] }, 'provider': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: spec_provider_url(transaction.provider), 'assertions': [ lambda input, transaction, output: input == output ] }, 'currency': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: transaction.currency, }, 'amount': { 'required': False, - 'expected_input_types': (int, float, text_type), + 'expected_input_types': (int, float, str), 'output': lambda transaction: "%.2f" % Decimal(transaction.amount), 'assertions': [ lambda input, transaction, output: ( @@ -53,7 +52,7 @@ }, 'state': { 'read_only': True, - 'output': lambda transaction: text_type(transaction.state), + 'output': lambda transaction: transaction.state, 'assertions': [ lambda input, transaction, output: output in [ 'initial', 'pending', 'settled', 'failed', 'canceled', 'refunded' @@ -61,7 +60,7 @@ ] }, 'invoice': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: ( spec_invoice_url(transaction.invoice) if transaction.invoice else None ), @@ -72,7 +71,7 @@ ] }, 'proforma': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: ( spec_proforma_url(transaction.proforma) if transaction.proforma else None ), @@ -120,7 +119,7 @@ ) }, 'payment_method': { - 'expected_input_types': text_type, + 'expected_input_types': str, 'output': lambda transaction: spec_payment_method_url(transaction.payment_method), 'assertions': [ lambda input, transaction, output: input == output diff --git a/silver/tests/api/specs/utils.py b/silver/tests/api/specs/utils.py index 42ec3d9f..ba418f58 100644 --- a/silver/tests/api/specs/utils.py +++ b/silver/tests/api/specs/utils.py @@ -1,7 +1,5 @@ import inspect -from django.utils.six import text_type - datetime_to_str = lambda input_date: input_date.isoformat()[:-6] + 'Z' datetime_to_str_or_none = lambda input_date: datetime_to_str(input_date) if input_date else None @@ -9,7 +7,6 @@ decimal_string_or_none = lambda input_decimal: ( None if input_decimal is None else "%.2f" % input_decimal ) -text_type_or_none = lambda input: text_type(input) if input else None class ResourceDefinition(object): diff --git a/silver/tests/api/test_documents.py b/silver/tests/api/test_documents.py index d04e6334..33a39b63 100644 --- a/silver/tests/api/test_documents.py +++ b/silver/tests/api/test_documents.py @@ -20,7 +20,7 @@ from freezegun import freeze_time from django.test import override_settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str from rest_framework import status from rest_framework.reverse import reverse @@ -44,7 +44,7 @@ def setUp(self): self.client.force_authenticate(user=admin_user) def _get_expected_data(self, document, transactions=None): - kind = force_text(document.kind.lower()) + kind = force_str(document.kind.lower()) transactions = [{ u'id': u'%s' % transaction.uuid, u'url': build_absolute_test_url(reverse('transaction-detail', @@ -84,8 +84,8 @@ def _get_expected_data(self, document, transactions=None): [document.provider.id])), u'customer': build_absolute_test_url(reverse('customer-detail', [document.customer.id])), - u'due_date': force_text(document.due_date) if document.due_date else None, - u'issue_date': force_text(document.issue_date) if document.issue_date else None, + u'due_date': force_str(document.due_date) if document.due_date else None, + u'issue_date': force_str(document.issue_date) if document.issue_date else None, u'paid_date': document.paid_date, u'cancel_date': document.cancel_date, u'sales_tax_name': document.sales_tax_name, diff --git a/silver/tests/api/test_invoice.py b/silver/tests/api/test_invoice.py index b3db7436..4d9ed58c 100644 --- a/silver/tests/api/test_invoice.py +++ b/silver/tests/api/test_invoice.py @@ -17,6 +17,7 @@ import json from datetime import timedelta from decimal import Decimal +from uuid import UUID import pytest from six.moves import range @@ -28,7 +29,6 @@ from django.db.models.signals import pre_save from django.utils import timezone -from django.utils.six import text_type from django.conf import settings from silver.models import Invoice, Transaction, DocumentEntry @@ -53,7 +53,7 @@ def test_post_invoice_without_invoice_entries(authenticated_api_client, customer 'customer': customer_url, 'series': None, 'number': None, - 'currency': text_type('RON'), + 'currency': 'RON', 'invoice_entries': [] } @@ -78,10 +78,10 @@ def test_post_invoice_with_invoice_entries(authenticated_api_client): 'customer': customer_url, 'series': None, 'number': None, - 'currency': text_type('RON'), + 'currency': 'RON', 'transaction_xe_rate': 1, 'invoice_entries': [{ - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20}] } @@ -112,10 +112,10 @@ def test_post_invoice_with_invoice_entries_without_transaction_xe_rate( 'customer': customer_url, 'series': None, 'number': None, - 'currency': text_type('RON'), - 'transaction_currency': text_type(transaction_currency), + 'currency': 'RON', + 'transaction_currency': transaction_currency, 'invoice_entries': [{ - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20}] } @@ -176,6 +176,13 @@ def test_get_invoice(authenticated_api_client, settings, issued_invoice): response = authenticated_api_client.get(url, format='json') assert response.status_code == status.HTTP_200_OK, response.data + + # Cast IDs to UUID so the comparison check doesn't fail + data = response.data + data['transactions'][0]['id'] = UUID(data['transactions'][0]['id']) + data['transactions'][1]['id'] = UUID(data['transactions'][1]['id']) + data['transactions'][2]['id'] = UUID(data['transactions'][2]['id']) + invoice_definition.check_response(invoice, response_data=response.data) @@ -190,7 +197,7 @@ def test_delete_invoice(authenticated_api_client): def test_add_single_invoice_entry(authenticated_api_client, invoice): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -222,9 +229,9 @@ def test_try_to_get_invoice_entries(authenticated_api_client, invoice): def test_add_multiple_invoice_entries(authenticated_api_client, invoice): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, - "quantity": text_type('20.0'), + "quantity": '20.0', } entries_count = 10 @@ -248,7 +255,7 @@ def test_delete_invoice_entry(authenticated_api_client): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -274,7 +281,7 @@ def test_add_invoice_entry_in_issued_state(authenticated_api_client): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -298,7 +305,7 @@ def test_add_invoice_entry_in_canceled_state(authenticated_api_client): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -322,7 +329,7 @@ def test_add_invoice_entry_in_paid_state(authenticated_api_client): url = reverse('invoice-entry-create', kwargs={'document_pk': invoice.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } diff --git a/silver/tests/api/test_payments.py b/silver/tests/api/test_payments.py index 5a502c87..100e31bb 100644 --- a/silver/tests/api/test_payments.py +++ b/silver/tests/api/test_payments.py @@ -20,7 +20,7 @@ from django.utils import timezone from django.template.loader import render_to_string from django.test import override_settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str from rest_framework import status from rest_framework.test import APITestCase @@ -47,7 +47,7 @@ def test_pay_transaction_view_expired(self): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, })) @@ -57,7 +57,7 @@ def test_pay_transaction_view_invalid_state(self): response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/complete_payment.html', { 'transaction': transaction, 'document': transaction.document, @@ -70,7 +70,7 @@ def test_pay_transaction_view_not_consumable_transaction(self): response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, })) @@ -88,7 +88,7 @@ def get_view(processor, transaction, request): response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, })) @@ -106,7 +106,7 @@ def get_view(processor, transaction, request): response = self.client.get(get_payment_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/expired_payment.html', { 'document': transaction.document, })) @@ -130,7 +130,7 @@ def test_complete_payment_view_without_return_url(self): response = self.client.get(get_payment_complete_url(transaction, None)) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(force_text(response.content), + self.assertEqual(force_str(response.content), render_to_string('transactions/complete_payment.html', { 'expired': False, 'transaction': transaction, diff --git a/silver/tests/api/test_proforma.py b/silver/tests/api/test_proforma.py index fd2d9e82..1f4f985d 100644 --- a/silver/tests/api/test_proforma.py +++ b/silver/tests/api/test_proforma.py @@ -26,7 +26,6 @@ from django.conf import settings from django.utils import timezone -from django.utils.six import text_type from silver.models import Invoice, Proforma, PDF from silver.fixtures.factories import (AdminUserFactory, CustomerFactory, @@ -56,7 +55,7 @@ def test_post_proforma_without_proforma_entries(self): data = { 'provider': provider_url, 'customer': customer_url, - 'currency': text_type('RON'), + 'currency': 'RON', 'proforma_entries': [] } @@ -72,15 +71,15 @@ def test_post_proforma_without_proforma_entries(self): "number": None, "provider": provider_url, "customer": customer_url, - "archived_provider": '{}', - "archived_customer": '{}', + "archived_provider": {}, + "archived_customer": {}, "due_date": None, "issue_date": None, "paid_date": None, "cancel_date": None, "sales_tax_name": "VAT", "sales_tax_percent": "1.00", - "currency": text_type("RON"), + "currency": "RON", "transaction_currency": proforma.transaction_currency, "transaction_xe_rate": (str(proforma.transaction_xe_rate) if proforma.transaction_xe_rate else None), @@ -108,10 +107,10 @@ def test_post_proforma_with_proforma_entries(self): 'customer': customer_url, 'series': None, 'number': None, - 'currency': text_type('RON'), + 'currency': 'RON', 'transaction_xe_rate': 1, 'proforma_entries': [{ - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 }] @@ -166,15 +165,15 @@ def test_get_proforma(self, mocked_settings): "number": proforma.number, "provider": provider_url, "customer": customer_url, - "archived_provider": '{}', - "archived_customer": '{}', + "archived_provider": {}, + "archived_customer": {}, "due_date": None, "issue_date": None, "paid_date": None, "cancel_date": None, "sales_tax_name": "VAT", "sales_tax_percent": '1.00', - "currency": text_type("RON"), + "currency": "RON", "transaction_currency": proforma.transaction_currency, "transaction_xe_rate": ("%.4f" % proforma.transaction_xe_rate if proforma.transaction_xe_rate else None), @@ -200,7 +199,7 @@ def test_add_single_proforma_entry(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -232,7 +231,7 @@ def test_add_multiple_proforma_entries(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) request_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -258,7 +257,7 @@ def test_delete_proforma_entry(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) entry_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -283,7 +282,7 @@ def test_add_proforma_entry_in_issued_state(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) entry_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -306,7 +305,7 @@ def test_add_proforma_entry_in_canceled_state(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) entry_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } @@ -329,7 +328,7 @@ def test_add_proforma_entry_in_paid_state(self): url = reverse('proforma-entry-create', kwargs={'document_pk': proforma.pk}) entry_data = { - "description": text_type("Page views"), + "description": "Page views", "unit_price": 10.0, "quantity": 20 } diff --git a/silver/tests/api/test_transactions.py b/silver/tests/api/test_transactions.py index e5459a40..629109e5 100644 --- a/silver/tests/api/test_transactions.py +++ b/silver/tests/api/test_transactions.py @@ -22,7 +22,7 @@ from django.utils import timezone from django.test import override_settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str from rest_framework import status from rest_framework.reverse import reverse as _reverse @@ -132,7 +132,7 @@ def test_add_transaction(self): self.assertEqual(response.data['valid_until'][:-1], valid_until.isoformat()) self.assertEqual(response.data['can_be_consumed'], True) self.assertEqual(response.data['amount'], - force_text(invoice.total_in_transaction_currency)) + force_str(invoice.total_in_transaction_currency)) self.assertEqual(response.data['invoice'], invoice_url) self.assertEqual(response.data['proforma'], proforma_url) self.assertEqual(response.data['currency'], currency) @@ -301,7 +301,7 @@ def test_add_transaction_without_currency_and_amount(self): self.assertEqual(response.data['valid_until'][:-1], valid_until.isoformat()) self.assertEqual(response.data['can_be_consumed'], True) self.assertEqual(response.data['amount'], - force_text(invoice.total_in_transaction_currency)) + force_str(invoice.total_in_transaction_currency)) self.assertEqual(response.data['invoice'], invoice_url) self.assertEqual(response.data['proforma'], proforma_url) self.assertEqual(response.data['currency'], invoice.transaction_currency) @@ -665,15 +665,15 @@ def _transaction_data(self, transaction): mocked_token.return_value = 'token' return OrderedDict([ - ('id', force_text(transaction.uuid)), + ('id', force_str(transaction.uuid)), ('url', reverse('transaction-detail', kwargs={'customer_pk': customer.id, 'transaction_uuid': transaction.uuid})), ('customer', reverse('customer-detail', args=[customer.pk])), ('provider', reverse('provider-detail', args=[provider.pk])), - ('amount', force_text(Decimal('0.00') + transaction.amount)), - ('currency', force_text(transaction.currency)), - ('state', force_text(transaction.state)), + ('amount', force_str(Decimal('0.00') + transaction.amount)), + ('currency', force_str(transaction.currency)), + ('state', force_str(transaction.state)), ('proforma', reverse('proforma-detail', args=[proforma.pk])), ('invoice', reverse('invoice-detail', args=[invoice.pk])), ('can_be_consumed', transaction.can_be_consumed), diff --git a/silver/tests/commands/test_generate_docs.py b/silver/tests/commands/test_generate_docs.py index 258e5e1b..453ad343 100644 --- a/silver/tests/commands/test_generate_docs.py +++ b/silver/tests/commands/test_generate_docs.py @@ -17,11 +17,12 @@ import datetime as dt from decimal import Decimal +from io import StringIO + from mock import patch, PropertyMock, MagicMock from django.core.management import call_command from django.test import TestCase -from django.utils.six import StringIO from silver.management.commands.generate_docs import date as generate_docs_date from silver.models import (Proforma, DocumentEntry, Invoice, Subscription, Customer, Plan, diff --git a/silver/tests/commands/test_generate_docs_args.py b/silver/tests/commands/test_generate_docs_args.py index 01317f21..dd84b402 100644 --- a/silver/tests/commands/test_generate_docs_args.py +++ b/silver/tests/commands/test_generate_docs_args.py @@ -16,10 +16,10 @@ from __future__ import absolute_import from decimal import Decimal +from io import StringIO from django.core.management import call_command from django.test import TestCase -from django.utils.six import StringIO from silver.management.commands.generate_docs import date as generate_docs_date from silver.models import (Plan) diff --git a/silver/tests/unit/test_payments_util.py b/silver/tests/unit/test_payments_util.py index 0a5d0112..2ca4836b 100644 --- a/silver/tests/unit/test_payments_util.py +++ b/silver/tests/unit/test_payments_util.py @@ -61,8 +61,8 @@ def test_get_transaction_from_token(self): mocked_view = MagicMock() token = _get_jwt_token(transaction) - self.assertEquals(get_transaction_from_token(mocked_view)(None, token), - mocked_view()) + self.assertEqual(get_transaction_from_token(mocked_view)(None, token), + mocked_view()) mocked_view.has_calls([call(None, transaction, False), call()]) def test_get_transaction_from_expired_token(self): @@ -73,6 +73,6 @@ def test_get_transaction_from_expired_token(self): mocked_datetime.utcnow.return_value = datetime.utcnow() - timedelta(days=2 * 365) token = _get_jwt_token(transaction) - self.assertEquals(get_transaction_from_token(mocked_view)(None, token), - mocked_view()) + self.assertEqual(get_transaction_from_token(mocked_view)(None, token), + mocked_view()) mocked_view.has_calls([call(None, transaction, True), call()]) diff --git a/silver/urls.py b/silver/urls.py index f210e7d8..47664140 100644 --- a/silver/urls.py +++ b/silver/urls.py @@ -17,8 +17,7 @@ from __future__ import absolute_import -from django.conf.urls import include, url -from django.contrib import admin +from django.conf.urls import include, re_path from silver.views import (pay_transaction_view, complete_payment_view, InvoiceAutocomplete, ProformaAutocomplete, @@ -26,30 +25,28 @@ CustomerAutocomplete, ProviderAutocomplete) -admin.autodiscover() urlpatterns = [ - url(r'^admin/', admin.site.urls), - url(r'^api-auth/', include('rest_framework.urls', + re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), - url(r'', include('silver.api.urls')), - - url(r'pay/(?P<token>[0-9a-zA-Z-_\.]+)/$', - pay_transaction_view, name='payment'), - url(r'pay/(?P<token>[0-9a-zA-Z-_\.]+)/complete$', - complete_payment_view, name='payment-complete'), - - url(r'^autocomplete/invoices/$', - InvoiceAutocomplete.as_view(), name='autocomplete-invoice'), - url(r'^autocomplete/proformas/$', - ProformaAutocomplete.as_view(), name='autocomplete-proforma'), - url(r'^autocomplete/payment-method/$', - PaymentMethodAutocomplete.as_view(), name='autocomplete-payment-method'), - url(r'^autocomplete/plan/$', - PlanAutocomplete.as_view(), name='autocomplete-plan'), - url(r'^autocomplete/customer/$', - CustomerAutocomplete.as_view(), name='autocomplete-customer'), - url(r'^autocomplete/provider/$', - ProviderAutocomplete.as_view(), name='autocomplete-provider'), + re_path(r'', include('silver.api.urls')), + + re_path(r'pay/(?P<token>[0-9a-zA-Z-_\.]+)/$', + pay_transaction_view, name='payment'), + re_path(r'pay/(?P<token>[0-9a-zA-Z-_\.]+)/complete$', + complete_payment_view, name='payment-complete'), + + re_path(r'^autocomplete/invoices/$', + InvoiceAutocomplete.as_view(), name='autocomplete-invoice'), + re_path(r'^autocomplete/proformas/$', + ProformaAutocomplete.as_view(), name='autocomplete-proforma'), + re_path(r'^autocomplete/payment-method/$', + PaymentMethodAutocomplete.as_view(), name='autocomplete-payment-method'), + re_path(r'^autocomplete/plan/$', + PlanAutocomplete.as_view(), name='autocomplete-plan'), + re_path(r'^autocomplete/customer/$', + CustomerAutocomplete.as_view(), name='autocomplete-customer'), + re_path(r'^autocomplete/provider/$', + ProviderAutocomplete.as_view(), name='autocomplete-provider'), ] diff --git a/silver/urls_project.py b/silver/urls_project.py new file mode 100644 index 00000000..c7d068f2 --- /dev/null +++ b/silver/urls_project.py @@ -0,0 +1,12 @@ +from django.urls import re_path +from django.contrib import admin + +from silver.urls import urlpatterns + +admin.autodiscover() + +# This urls.py file should be used when running silver as a standalone +# project (e.g. when running silver's tests) +urlpatterns = [ + re_path(r'^admin/', admin.site.urls), +] + urlpatterns diff --git a/silver/utils/decorators.py b/silver/utils/decorators.py index bc83c3f2..c7bc3d65 100644 --- a/silver/utils/decorators.py +++ b/silver/utils/decorators.py @@ -28,10 +28,12 @@ def decorator(request, token): try: expired = False transaction_uuid = jwt.decode(token, - settings.PAYMENT_METHOD_SECRET)['transaction'] + settings.PAYMENT_METHOD_SECRET, + algorithms=['HS256'])['transaction'] except jwt.ExpiredSignatureError: expired = True transaction_uuid = jwt.decode(token, settings.PAYMENT_METHOD_SECRET, + algorithms=['HS256'], options={'verify_exp': False})['transaction'] try: diff --git a/silver/utils/payments.py b/silver/utils/payments.py index 9b09a4c4..2df48eca 100644 --- a/silver/utils/payments.py +++ b/silver/utils/payments.py @@ -23,15 +23,15 @@ from furl import furl from django.conf import settings -from django.utils.encoding import force_text +from django.utils.encoding import force_str from rest_framework.reverse import reverse def _get_jwt_token(transaction): valid_until = datetime.utcnow() + settings.SILVER_PAYMENT_TOKEN_EXPIRATION - data = {'transaction': force_text(transaction.uuid), 'exp': valid_until} - return force_text(jwt.encode(data, settings.PAYMENT_METHOD_SECRET)) + data = {'transaction': force_str(transaction.uuid), 'exp': valid_until} + return force_str(jwt.encode(data, settings.PAYMENT_METHOD_SECRET)) def get_payment_url(transaction, request):