From dadb31bb60639fcb659e3d24f59b14323801874d Mon Sep 17 00:00:00 2001 From: gsvr Date: Wed, 4 Dec 2024 12:41:46 +0200 Subject: [PATCH] update maguire --- .github/workflows/gcp_publish.yml | 64 ++++--- backend/Dockerfile | 5 +- backend/debits/models.py | 2 +- backend/debits/schema.py | 86 +++++---- backend/events/models.py | 2 +- backend/events/schema.py | 71 +++----- backend/maguire/settings.py | 4 +- backend/maguire/urls.py | 23 ++- backend/maguire/utils.py | 25 ++- backend/requirements-dev.in | 11 ++ backend/requirements-dev.txt | 280 +++++++++++++++++++++++++++++- backend/requirements.in | 37 ++++ backend/requirements.txt | 185 +++++++++++++++++++- backend/setup.py | 27 +-- 14 files changed, 642 insertions(+), 180 deletions(-) create mode 100644 backend/requirements-dev.in create mode 100644 backend/requirements.in diff --git a/.github/workflows/gcp_publish.yml b/.github/workflows/gcp_publish.yml index 3070300..f530906 100644 --- a/.github/workflows/gcp_publish.yml +++ b/.github/workflows/gcp_publish.yml @@ -1,4 +1,6 @@ -name: Build and Deploy to GKE (Google Kubernetes Engine) +# This workflow will build a docker container and publish it to Google Container Registry + +name: Production build and publish on: push: @@ -6,43 +8,35 @@ on: - master env: + REGISTRY_HOSTNAME: eu.gcr.io GKE_PROJECT: ${{ secrets.GKE_PROJECT }} - GKE_EMAIL: ${{ secrets.GKE_EMAIL }} GITHUB_SHA: ${{ github.sha }} - IMAGE: maguire-backend - REGISTRY_HOSTNAME: eu.gcr.io - DEPLOYMENT_NAME: gke-test jobs: - setup-build-publish: - name: Setup, Build, Publish + backend: + name: build and publish backend runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v0 - with: - version: '270.0.0' - service_account_email: ${{ secrets.GKE_EMAIL }} - service_account_key: ${{ secrets.GKE_KEY }} - - # Configure docker to use the gcloud command-line tool as a credential helper - - run: | - # Set up docker to authenticate - # via gcloud command-line tool. - gcloud auth configure-docker - - # Build the Docker image - - name: Build Maguire Backend - run: | - docker build -t "$REGISTRY_HOSTNAME"/"$GKE_PROJECT"/"$IMAGE":"$GITHUB_SHA" \ - --build-arg GITHUB_SHA="$GITHUB_SHA" \ - --build-arg GITHUB_REF="$GITHUB_REF" backend - - # Push the Docker image to Google Container Registry - - name: Publish Maguire Backend - run: | - docker push $REGISTRY_HOSTNAME/$GKE_PROJECT/$IMAGE:$GITHUB_SHA + - name: Check out repository + uses: actions/checkout@v4 + + - id: auth + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GKE_KEY }} + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Setup docker gcloud auth + run: | + # configure docker to authenticate via gcloud command-line tool + gcloud auth configure-docker + + - name: Build backend docker image + run: | + docker build -t "$REGISTRY_HOSTNAME"/"$GKE_PROJECT"/maguire-backend:"$GITHUB_SHA" backend + + - name: Publish backend docker image + run: | + docker push $REGISTRY_HOSTNAME/$GKE_PROJECT/maguire-backend:$GITHUB_SHA diff --git a/backend/Dockerfile b/backend/Dockerfile index f72efc4..767c626 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,6 +1,7 @@ -FROM praekeltfoundation/django-bootstrap:py3.8-buster +FROM ghcr.io/praekeltfoundation/docker-django-bootstrap-nw:py3.10-bullseye COPY . /app +RUN pip install -r requirements.txt RUN pip install -e . ENV SENTRY_RELEASE=${VCS_HASH} @@ -8,4 +9,4 @@ ENV DJANGO_SETTINGS_MODULE maguire.settings ENV CELERY_APP maguire RUN django-admin collectstatic --noinput -CMD ["maguire.wsgi:application", "--threads", "5", "--timeout", "300"] +CMD ["maguire.wsgi:application", "--workers", "2", "--threads", "3", "--timeout", "420", "--max-requests", "1000", "--max-requests-jitter", "100", "--graceful-timeout", "300"] diff --git a/backend/debits/models.py b/backend/debits/models.py index f8e3da2..ee2f3a3 100644 --- a/backend/debits/models.py +++ b/backend/debits/models.py @@ -6,7 +6,7 @@ from django.dispatch import receiver from django.db.models.signals import post_save from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from maguire.models import AppModel diff --git a/backend/debits/schema.py b/backend/debits/schema.py index 50f650b..9c494ff 100644 --- a/backend/debits/schema.py +++ b/backend/debits/schema.py @@ -1,62 +1,57 @@ from django.contrib.contenttypes.models import ContentType import django_filters -from graphene import relay, AbstractType, String, Field +from graphene import relay, String, Field from graphene.types.datetime import DateTime from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField from django_filters import OrderingFilter from .models import Debit -from maguire.utils import (uuid_from_b64, schema_get_mutation_data, schema_update_model, - schema_define_user, schema_create_updated_event, TotalCountMixin) +from maguire.utils import ( + TotalCountMixin, + get_node_with_permission, + schema_create_updated_event, + schema_define_user, + schema_get_mutation_data, + schema_update_model, + uuid_from_b64, +) -# Graphene will automatically map the Debit model's fields onto the DebitNode. -# This is configured in the DebitNode's Meta class (as you can see below) - -DEBIT_FILTERS = { - 'branch_code': ['exact', 'icontains', 'istartswith'], - 'account_number': ['exact', 'icontains', 'istartswith'], - 'account_name': ['exact', 'icontains', 'istartswith'], - 'provider': ['exact'], - 'client': ['exact'], - 'downstream_reference': ['exact', 'icontains', 'istartswith'], - 'reference': ['exact', 'icontains', 'istartswith'], - 'provider_reference': ['exact', 'icontains', 'istartswith'], - 'amount': ['lt', 'gt', 'lte', 'gte', 'exact'], -} - +class DebitFilter(django_filters.FilterSet): + # Make CharField with choices filtering case-insensitive str lookup + account_type = django_filters.CharFilter( + field_name='account_type', lookup_expr='iexact') + status = django_filters.CharFilter( + field_name='status', lookup_expr='iexact') -class DebitNode(DjangoObjectType, TotalCountMixin): class Meta: model = Debit - # Allow for some more advanced filtering here - filter_fields = DEBIT_FILTERS - interfaces = (relay.Node, ) + fields = { + 'branch_code': ['exact', 'icontains', 'istartswith'], + 'account_number': ['exact', 'icontains', 'istartswith'], + 'account_name': ['exact', 'icontains', 'istartswith'], + 'provider': ['exact'], + 'client': ['exact'], + 'downstream_reference': ['exact', 'icontains', 'istartswith'], + 'reference': ['exact', 'icontains', 'istartswith'], + 'provider_reference': ['exact', 'icontains', 'istartswith'], + 'amount': ['lt', 'gt', 'lte', 'gte', 'exact'], + } - @classmethod - def get_node(cls, info, id): - # HTTP request - try: - node = cls._meta.model.objects.get(id=id) - except cls._meta.model.DoesNotExist: - return cls._meta.model.objects.none() - if info.context is not None: - if info.context.user.is_authenticated: - return node - else: - return cls._meta.model.objects.none() - else: # Not a HTTP request - no permissions testing currently - return node + order_by = OrderingFilter(fields=['created_at', 'scheduled_at', 'loaded_at']) -class DebitFilter(django_filters.FilterSet): +class DebitNode(DjangoObjectType, TotalCountMixin): class Meta: model = Debit - fields = DEBIT_FILTERS + filterset_class = DebitFilter + interfaces = (relay.Node, ) - order_by = OrderingFilter(fields=['created_at', 'scheduled_at', 'loaded_at']) + @classmethod + def get_node(cls, info, id): + return get_node_with_permission(cls, id, info.context) class DebitMutation(relay.ClientIDMutation): @@ -113,14 +108,13 @@ def mutate_and_get_payload(cls, root, info, **input): return DebitMutation(debit=debit) -class Query(AbstractType): +class Query(object): debit = relay.Node.Field(DebitNode) - debits = DjangoFilterConnectionField(DebitNode, filterset_class=DebitFilter) + debits = DjangoFilterConnectionField(DebitNode) - def resolve_debits(self, args, context, info): - # context will reference to the Django request - if context is not None: - if context.user.is_authenticated: + def resolve_debits(self, info, **args): + if info.context is not None: + if info.context.user.is_authenticated: return DebitFilter(args, queryset=Debit.objects.all()).qs else: return Debit.objects.none() @@ -128,5 +122,5 @@ def resolve_debits(self, args, context, info): return DebitFilter(args, queryset=Debit.objects.all()).qs -class Mutation(AbstractType): +class Mutation(object): debit_mutate = DebitMutation.Field() diff --git a/backend/events/models.py b/backend/events/models.py index e20cc5b..bb52eea 100644 --- a/backend/events/models.py +++ b/backend/events/models.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from django.utils.timezone import now from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey diff --git a/backend/events/schema.py b/backend/events/schema.py index bde8177..32992e2 100644 --- a/backend/events/schema.py +++ b/backend/events/schema.py @@ -1,59 +1,40 @@ import django_filters from graphql import GraphQLError -from graphene import relay, AbstractType, String, Field, Int +from graphene import relay, String, Field, Int from graphene.types.datetime import DateTime from graphene.types.json import JSONString from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField from django_filters import OrderingFilter -from rolepermissions.verifications import has_object_permission, has_permission +from rolepermissions.verifications import has_permission from .models import Event -from maguire.utils import (schema_get_mutation_data) +from maguire.utils import get_node_with_permission, schema_get_mutation_data -# Graphene will automatically map the Event model's fields onto the EventNode. -# This is configured in the EventNode's Meta class (as you can see below) - -EVENT_FILTERS = { - 'source_model': ['exact'], - 'source_id': ['exact'], - 'event_type': ['exact'], -} - +class EventFilter(django_filters.FilterSet): -class EventNode(DjangoObjectType): class Meta: model = Event - # Allow for some more advanced filtering here - filter_fields = EVENT_FILTERS - interfaces = (relay.Node, ) - - @classmethod - def get_node(cls, id, context, info): - # HTTP request - try: - node = cls._meta.model.objects.get(id=id) - except cls._meta.model.DoesNotExist: - return cls._meta.model.objects.none() - if context is not None: - if context.user.is_authenticated and ( - has_object_permission('access_event', context.user, node)): - return node - else: - return cls._meta.model.objects.none() - else: # Not a HTTP request - no permissions testing currently - return node + fields = { + 'source_model': ['exact'], + 'source_id': ['exact'], + 'event_type': ['exact'], + } + order_by = OrderingFilter(fields=['created_at']) -class EventFilter(django_filters.FilterSet): +class EventNode(DjangoObjectType): class Meta: model = Event - fields = EVENT_FILTERS + filterset_class = EventFilter + interfaces = (relay.Node, ) - order_by = OrderingFilter(fields=['created_at']) + @classmethod + def get_node(cls, info, id): + return get_node_with_permission(cls, id, info.context, 'access_event') class EventMutation(relay.ClientIDMutation): @@ -84,17 +65,15 @@ def mutate_and_get_payload(cls, input, context, info): return EventMutation(event=event) -class Query(AbstractType): +class Query(object): event = relay.Node.Field(EventNode) - events = DjangoFilterConnectionField(EventNode, - filterset_class=EventFilter) - - def resolve_events(self, args, context, info): - # context will reference to the Django request - if context is not None: - if context.user.is_authenticated and ( - has_permission(context.user, 'list_all') or - has_permission(context.user, 'list_events')): + events = DjangoFilterConnectionField(EventNode) + + def resolve_events(self, info, **args): + if info.context is not None: + if info.context.user.is_authenticated and ( + has_permission(info.context.user, 'list_all') or + has_permission(info.context.user, 'list_events')): return EventFilter(args, queryset=Event.objects.all()).qs else: return Event.objects.none() @@ -102,5 +81,5 @@ def resolve_events(self, args, context, info): return EventFilter(args, queryset=Event.objects.all()).qs -class Mutation(AbstractType): +class Mutation(object): event_mutate = EventMutation.Field() diff --git a/backend/maguire/settings.py b/backend/maguire/settings.py index 5ba5854..2cc84c6 100644 --- a/backend/maguire/settings.py +++ b/backend/maguire/settings.py @@ -47,7 +47,7 @@ 'raven.contrib.django.raven_compat', 'rest_framework', 'rest_framework.authtoken', - 'rest_auth', + 'dj_rest_auth', 'django_filters', 'corsheaders', 'reversion', @@ -133,8 +133,6 @@ USE_I18N = True -USE_L10N = True - USE_TZ = True diff --git a/backend/maguire/urls.py b/backend/maguire/urls.py index a6e5d1d..b2ee4fa 100644 --- a/backend/maguire/urls.py +++ b/backend/maguire/urls.py @@ -1,15 +1,20 @@ import os -from django.conf.urls import include, url + +from django.conf import settings +from django.conf.urls import include +from django.conf.urls.static import static from django.contrib import admin -from django.views.decorators.csrf import csrf_exempt from django.contrib.admin.views.decorators import staff_member_required +from django.urls import re_path +from django.views.decorators.csrf import csrf_exempt + from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.decorators import authentication_classes, permission_classes, api_view + from graphene_django.views import GraphQLView + from maguire.schema import schema -from django.conf import settings -from django.conf.urls.static import static admin.site.site_header = os.environ.get('MAGUIRE_TITLE', 'Maguire Admin') @@ -23,10 +28,10 @@ def graphql_token_view(): urlpatterns = [ - url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - url(r'^admin/', admin.site.urls), - url(r'^graphql', graphql_token_view()), - url(r'^graphiql', staff_member_required(csrf_exempt( + re_path(r'^admin/doc/', include('django.contrib.admindocs.urls')), + re_path(r'^admin/', admin.site.urls), + re_path(r'^graphql', graphql_token_view()), + re_path(r'^graphiql', staff_member_required(csrf_exempt( GraphQLView.as_view(schema=schema, graphiql=True)))), - url(r'^api/rest-auth/', include('rest_auth.urls')), + re_path(r'^api/rest-auth/', include('dj_rest_auth.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/backend/maguire/utils.py b/backend/maguire/utils.py index 8da5d4d..abd1d7e 100644 --- a/backend/maguire/utils.py +++ b/backend/maguire/utils.py @@ -9,10 +9,11 @@ from django.contrib.contenttypes.models import ContentType from django.utils import timezone +from rolepermissions.checkers import has_object_permission + import boto3 from botocore.client import Config - from graphene import relay, Int, ObjectType from events.models import Event @@ -94,6 +95,28 @@ def parse_schema_fk_fields(mutation_data, fk_fields, input): return mutation_data +def get_node_with_permission(cls, id, context, access_param=None): + # HTTP request + try: + node = cls._meta.model.objects.get(id=id) + except cls._meta.model.DoesNotExist: + return cls._meta.model.objects.none() + if context is not None: + if access_param: + if context.user.is_authenticated and has_object_permission( + access_param, context.user, node): + return node + else: + return cls._meta.model.objects.none() + else: + if context.user.is_authenticated: + return node + else: + return cls._meta.model.objects.none() + else: # Not a HTTP request - no permissions testing currently + return node + + def schema_get_mutation_data(fk_fields, non_fk_fields, input, context, update): mutation_data = {} # . FK fields diff --git a/backend/requirements-dev.in b/backend/requirements-dev.in new file mode 100644 index 0000000..989db2c --- /dev/null +++ b/backend/requirements-dev.in @@ -0,0 +1,11 @@ +pytest +pytest-cov +pytest-django +pytest-env +pytest-instafail +pytest-xdist +flake8 +responses +moto +freezegun +urllib3 diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 48a18fe..9e026eb 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -1,9 +1,271 @@ -freezegun -pytest -pytest-cov -pytest-django -pytest-env -pytest-xdist -flake8 -responses -moto +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in requirements-dev.in -o requirements-dev.txt +amqp==5.3.1 + # via kombu +asgiref==3.8.1 + # via + # django + # django-cors-headers +asttokens==3.0.0 + # via stack-data +billiard==4.2.1 + # via celery +boto3==1.35.74 + # via + # -r requirements.in + # moto +botocore==1.35.74 + # via + # boto3 + # moto + # s3transfer +celery==5.4.0 + # via + # -r requirements.in + # django-celery-beat +certifi==2024.8.30 + # via requests +cffi==1.17.1 + # via cryptography +charset-normalizer==3.4.0 + # via requests +click==8.1.7 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl +click-didyoumean==0.3.1 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.3.0 + # via celery +coverage==7.6.8 + # via pytest-cov +cron-descriptor==1.4.5 + # via django-celery-beat +cryptography==44.0.0 + # via moto +decorator==5.1.1 + # via ipython +dj-database-url==2.3.0 + # via -r requirements.in +dj-rest-auth==7.0.0 + # via -r requirements.in +django==4.2.16 + # via + # -r requirements.in + # dj-database-url + # dj-rest-auth + # django-celery-beat + # django-cors-headers + # django-extensions + # django-filter + # django-reversion + # django-role-permissions + # django-storages + # django-timezone-field + # djangorestframework + # graphene-django +django-celery-beat==2.7.0 + # via -r requirements.in +django-cors-headers==4.6.0 + # via -r requirements.in +django-extensions==3.2.3 + # via -r requirements.in +django-filter==24.3 + # via -r requirements.in +django-reversion==5.1.0 + # via -r requirements.in +django-role-permissions==3.2.0 + # via -r requirements.in +django-storages==1.14.4 + # via -r requirements.in +django-timezone-field==7.0 + # via django-celery-beat +djangorestframework==3.15.2 + # via + # -r requirements.in + # dj-rest-auth +exceptiongroup==1.2.2 + # via + # ipython + # pytest +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +flake8==7.1.1 + # via -r requirements-dev.in +freezegun==1.5.1 + # via -r requirements-dev.in +graphene==3.4.3 + # via graphene-django +graphene-django==3.2.2 + # via -r requirements.in +graphql-core==3.2.5 + # via + # graphene + # graphene-django + # graphql-relay +graphql-relay==3.2.0 + # via + # graphene + # graphene-django +idna==3.10 + # via requests +iniconfig==2.0.0 + # via pytest +ipython==8.30.0 + # via -r requirements.in +jedi==0.19.2 + # via ipython +jinja2==3.1.4 + # via moto +jmespath==1.0.1 + # via + # boto3 + # botocore +kombu==5.4.2 + # via celery +markupsafe==3.0.2 + # via + # jinja2 + # werkzeug +matplotlib-inline==0.1.7 + # via ipython +mccabe==0.7.0 + # via flake8 +moto==5.0.22 + # via -r requirements-dev.in +packaging==24.2 + # via pytest +parso==0.8.4 + # via jedi +pendulum==3.0.0 + # via -r requirements.in +pexpect==4.9.0 + # via ipython +pluggy==1.5.0 + # via pytest +pprintpp==0.4.0 + # via -r requirements.in +promise==2.3 + # via graphene-django +prompt-toolkit==3.0.48 + # via + # click-repl + # ipython +psycopg==3.2.3 + # via -r requirements.in +psycopg-binary==3.2.3 + # via psycopg +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pycodestyle==2.12.1 + # via flake8 +pycparser==2.22 + # via cffi +pyflakes==3.2.0 + # via flake8 +pygments==2.18.0 + # via ipython +pytest==8.3.4 + # via + # -r requirements-dev.in + # pytest-cov + # pytest-django + # pytest-env + # pytest-instafail + # pytest-xdist +pytest-cov==6.0.0 + # via -r requirements-dev.in +pytest-django==4.9.0 + # via -r requirements-dev.in +pytest-env==1.1.5 + # via -r requirements-dev.in +pytest-instafail==0.5.0 + # via -r requirements-dev.in +pytest-xdist==3.6.1 + # via -r requirements-dev.in +python-crontab==3.2.0 + # via django-celery-beat +python-dateutil==2.9.0.post0 + # via + # -r requirements.in + # botocore + # celery + # freezegun + # graphene + # moto + # pendulum + # python-crontab + # time-machine +pyyaml==6.0.2 + # via responses +raven==6.10.0 + # via -r requirements.in +requests==2.32.3 + # via + # moto + # responses +responses==0.25.3 + # via + # -r requirements-dev.in + # moto +s3transfer==0.10.4 + # via boto3 +six==1.16.0 + # via + # promise + # python-dateutil +sqlparse==0.5.2 + # via django +stack-data==0.6.3 + # via ipython +text-unidecode==1.3 + # via graphene-django +time-machine==2.16.0 + # via pendulum +tomli==2.2.1 + # via + # coverage + # pytest + # pytest-env +traitlets==5.14.3 + # via + # ipython + # matplotlib-inline +typing-extensions==4.12.2 + # via + # asgiref + # dj-database-url + # graphene + # ipython + # psycopg +tzdata==2024.2 + # via + # celery + # django-celery-beat + # kombu + # pendulum +urllib3==2.2.3 + # via + # -r requirements-dev.in + # botocore + # requests + # responses +vine==5.1.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.13 + # via prompt-toolkit +werkzeug==3.1.3 + # via moto +xmltodict==0.14.2 + # via moto diff --git a/backend/requirements.in b/backend/requirements.in new file mode 100644 index 0000000..d9e5dd3 --- /dev/null +++ b/backend/requirements.in @@ -0,0 +1,37 @@ +# Django +# https://www.djangoproject.com/download +Django<5 + +# Django related +django-cors-headers +django-filter +django-extensions +django-reversion +django-role-permissions +django-storages +djangorestframework +dj-database-url +dj-rest-auth + +psycopg[binary] + +# Sentry +raven + +# Celery +celery +django-celery-beat + +# Date handling +python-dateutil +pendulum + +# GraphQL +graphene-django + +# AWS +boto3 + +# Other +ipython +pprintpp==0.4.0 # pinned for security diff --git a/backend/requirements.txt b/backend/requirements.txt index 0b5bd7a..2764b41 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,2 +1,183 @@ -# Our dependencies are all specified in setup.py. --e . +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +amqp==5.3.1 + # via kombu +asgiref==3.8.1 + # via + # django + # django-cors-headers +asttokens==3.0.0 + # via stack-data +billiard==4.2.1 + # via celery +boto3==1.35.74 + # via -r requirements.in +botocore==1.35.74 + # via + # boto3 + # s3transfer +celery==5.4.0 + # via + # -r requirements.in + # django-celery-beat +click==8.1.7 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl +click-didyoumean==0.3.1 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.3.0 + # via celery +cron-descriptor==1.4.5 + # via django-celery-beat +decorator==5.1.1 + # via ipython +dj-database-url==2.3.0 + # via -r requirements.in +dj-rest-auth==7.0.0 + # via -r requirements.in +django==4.2.16 + # via + # -r requirements.in + # dj-database-url + # dj-rest-auth + # django-celery-beat + # django-cors-headers + # django-extensions + # django-filter + # django-reversion + # django-role-permissions + # django-storages + # django-timezone-field + # djangorestframework + # graphene-django +django-celery-beat==2.7.0 + # via -r requirements.in +django-cors-headers==4.6.0 + # via -r requirements.in +django-extensions==3.2.3 + # via -r requirements.in +django-filter==24.3 + # via -r requirements.in +django-reversion==5.1.0 + # via -r requirements.in +django-role-permissions==3.2.0 + # via -r requirements.in +django-storages==1.14.4 + # via -r requirements.in +django-timezone-field==7.0 + # via django-celery-beat +djangorestframework==3.15.2 + # via + # -r requirements.in + # dj-rest-auth +exceptiongroup==1.2.2 + # via ipython +executing==2.1.0 + # via stack-data +graphene==3.4.3 + # via graphene-django +graphene-django==3.2.2 + # via -r requirements.in +graphql-core==3.2.5 + # via + # graphene + # graphene-django + # graphql-relay +graphql-relay==3.2.0 + # via + # graphene + # graphene-django +ipython==8.30.0 + # via -r requirements.in +jedi==0.19.2 + # via ipython +jmespath==1.0.1 + # via + # boto3 + # botocore +kombu==5.4.2 + # via celery +matplotlib-inline==0.1.7 + # via ipython +parso==0.8.4 + # via jedi +pendulum==3.0.0 + # via -r requirements.in +pexpect==4.9.0 + # via ipython +pprintpp==0.4.0 + # via -r requirements.in +promise==2.3 + # via graphene-django +prompt-toolkit==3.0.48 + # via + # click-repl + # ipython +psycopg==3.2.3 + # via -r requirements.in +psycopg-binary==3.2.3 + # via psycopg +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pygments==2.18.0 + # via ipython +python-crontab==3.2.0 + # via django-celery-beat +python-dateutil==2.9.0.post0 + # via + # -r requirements.in + # botocore + # celery + # graphene + # pendulum + # python-crontab + # time-machine +raven==6.10.0 + # via -r requirements.in +s3transfer==0.10.4 + # via boto3 +six==1.16.0 + # via + # promise + # python-dateutil +sqlparse==0.5.2 + # via django +stack-data==0.6.3 + # via ipython +text-unidecode==1.3 + # via graphene-django +time-machine==2.16.0 + # via pendulum +traitlets==5.14.3 + # via + # ipython + # matplotlib-inline +typing-extensions==4.12.2 + # via + # asgiref + # dj-database-url + # graphene + # ipython + # psycopg +tzdata==2024.2 + # via + # celery + # django-celery-beat + # kombu + # pendulum +urllib3==2.2.3 + # via botocore +vine==5.1.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.13 + # via prompt-toolkit diff --git a/backend/setup.py b/backend/setup.py index 5667918..8ba406b 100644 --- a/backend/setup.py +++ b/backend/setup.py @@ -9,31 +9,8 @@ author_email='admin@picsa.com', packages=find_packages(), include_package_data=True, - install_requires=[ - 'Django<4', - 'django-celery-beat', - 'django-cors-headers', - 'django-filter', - 'django-extensions', - 'django-rest-auth', - 'django-reversion', - 'django-role-permissions', - 'django-storages', - 'djangorestframework', - 'dj-database-url', - 'psycopg2-binary', - 'raven', - 'gunicorn', - 'whitenoise', - 'celery', - 'redis', - 'pytz', - 'python-dateutil', - 'graphene-django', - 'pendulum', - 'postmarker', - 'boto3', - ], + # dependencies are declared in requirements.in, locked in requirements.txt + install_requires=[], classifiers=[ 'Development Status :: 4 - Beta', 'Framework :: Django',