From 1ad40bc49d67ea6539cca2061af84f89994dd0af Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 26 Jul 2024 14:42:26 +0200 Subject: [PATCH 1/2] Updated Django version and readme --- .gitignore | 2 + README.md | 64 ++++++++++++------------------ myapp/{init.py => __init__.py} | 0 myapp/views.py | 50 +++++++++++------------ myproject/{init.py => __init__.py} | 0 myproject/settings.py | 13 +++--- requirements.txt | 8 ++-- 7 files changed, 63 insertions(+), 74 deletions(-) rename myapp/{init.py => __init__.py} (100%) rename myproject/{init.py => __init__.py} (100%) diff --git a/.gitignore b/.gitignore index 024a092..39189a7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ migrations/ db.sqlite3 .vscode .DS_Store +.venv/ +.python-version \ No newline at end of file diff --git a/README.md b/README.md index 4d25e97..7d52ef3 100644 --- a/README.md +++ b/README.md @@ -4,35 +4,37 @@ - [Running The Demo](#running-the-demo) - [Cleaning Up](#cleaning-up) + ## Installing Dependencies This project uses Django 2.2 that requires Python 3 -1. Install Python 3: -``` -brew install python3 +1. Install Python: +```bash +brew install python ``` -2. Install `virtualenv` and `virtualenvwrapper` +2. Create virtual environment +```bash +python -m venv .venv ``` -pip3 install virtualenv virtualenvwrapper -echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc -exec bash + +3. Activate virtual environment +```bash +source .venv/bin/activate ``` 3. Install Sentry's command line tool to use release tracking and Github integration for commit data: -``` -npm install -g @sentry/cli +```bash +python -m pip install "sentry-cli" ``` -This demo uses npm, pip, and virtualenv. ## Configuring Sentry -The Sentry client library requires a [DSN generated from Sentry](https://docs.sentry.io/quickstart/#configure-the-dsn) which specifies the project events will be sent to. Add the import and configuration code to `settings.py`: +The Sentry client library requires a [DSN generated from Sentry](https://docs.sentry.io/quickstart/#configure-the-dsn) which specifies the project events will be sent to. Add the import and configuration code to `settings.py`: - ``` +```python import sentry_sdk -from sentry_sdk.integrations.django import DjangoIntegration sentry_sdk.init( dsn="https://yourdsn@sentry.io/1234567" @@ -41,27 +43,12 @@ sentry_sdk.init( Further details on configuring Sentry [here](https://docs.sentry.io/platforms/python/django/). -## Running The Demo - -Create a python-3 virtualenv (see below) and run `deploy` target in `Makefile`. - -### Virtualenv setup - -Setup and activate a Python 3 virtual environment in the project root: -``` -mkvirtualenv --python=python3 sentry-demo-django -``` - -To use virtualenv: -``` -workon sentry-demo-django -``` +## Running The Demo -### Runing Django +Run `deploy` target in `Makefile`. This will install relevant python libraries and run Django server. -Running the following command will install relevant python libraries and run django server -``` +```bash make deploy ``` @@ -69,13 +56,13 @@ make deploy ### Demo Specs This demo uses Django's rest-framework package and offers 3 API endpoints: -1. http://localhost:8000/handled - generates a runtime error excplicitly reported to Sentry though the SDk's captureException +1. http://localhost:8000/handled - generates a runtime error explicitly reported to Sentry though the SDK's `captureException` 2. http://localhost:8000/unhandled - generates an unhandled runtime error reported 3. http://localhost:8000/checkout - can be used with the [Sentry REACT demo store front demo](https://github.com/sentry-demos/react) This endpoint can also be used with directly through the Django REST Framework web UI. To generate an error paste the following JSON payload in the POST payload text area: -``` +```json { "cart": [ {"id": "wrench", "name": "Wrench", "price": 500}, @@ -87,16 +74,17 @@ This demo uses Django's rest-framework package and offers 3 API endpoints: ![Alt Text](django_demo_setup.gif) + ## Cleaning Up Pressing Ctrl-C once in each terminal window should stop Django's development server. -To deactivate the virtualenv sentry-demo-django: -``` +To deactivate the virtual environment: +```bash deactivate ``` -To remove the virtualenv: -``` -rmvirtualenv sentry-demo-django +To remove the virtual environment: +```bash +rm -rf .venv/ ``` diff --git a/myapp/init.py b/myapp/__init__.py similarity index 100% rename from myapp/init.py rename to myapp/__init__.py diff --git a/myapp/views.py b/myapp/views.py index d137cb5..601352c 100644 --- a/myapp/views.py +++ b/myapp/views.py @@ -1,17 +1,20 @@ -from __future__ import unicode_literals - -import sentry_sdk import json + from rest_framework.response import Response from rest_framework.views import APIView from .serializers import InventorySerializer from .models import Inventory -from sentry_sdk import add_breadcrumb -InventoryData = [{"name": "wrench", "count": 1}, - {"name": "nails", "count": 1}, - {"name": "hammer", "count": 1}] +import sentry_sdk + + +InventoryData = [ + {"name": "wrench", "count": 1}, + {"name": "nails", "count": 1}, + {"name": "hammer", "count": 1}, +] + def find_in_inventory(itemId): for item in InventoryData: @@ -19,8 +22,9 @@ def find_in_inventory(itemId): return item raise Exception("Item : " + itemId + " not in inventory ") + def process_order(cart): - add_breadcrumb( + sentry_sdk.add_breadcrumb( category='Process Order', message='Step taken to process an order', level='info', @@ -31,12 +35,13 @@ def process_order(cart): itemID = item['id'] inventoryItem = find_in_inventory(itemID) if inventoryItem['count'] <= 0: - raise Exception("Not enough inventory for " + itemID) + raise Exception("Not enough inventory for " + itemID) else: inventoryItem['count'] -= 1 print( 'Success: ' + itemID + ' was purchased, remaining stock is ' + str(inventoryItem['count']) ) InventoryData = tempInventory + class SentryContextMixin(object): def dispatch(self, request, *args, **kwargs): @@ -46,9 +51,9 @@ def dispatch(self, request, *args, **kwargs): with sentry_sdk.configure_scope() as scope: scope.user = { "email" : order["email"] } - + transactionId = request.headers.get('X-Transaction-ID') - + global InventoryData with sentry_sdk.configure_scope() as scope: @@ -60,7 +65,7 @@ def dispatch(self, request, *args, **kwargs): return super(SentryContextMixin, self).dispatch(request, *args, **kwargs) -# Create your views here. + class InventoreyView(SentryContextMixin, APIView): def get(self, request): @@ -73,16 +78,16 @@ def post(self, request, format=None): order = json.loads(body_unicode) cart = order['cart'] process_order(cart) - return Response(InventoryData) + return Response(InventoryData) class HandledErrorView(APIView): def get(self, request): - add_breadcrumb( + sentry_sdk.add_breadcrumb( category='URL Endpoints', message='In the handled function', level='info', - ) + ) try: '2' + 2 except Exception as err: @@ -91,27 +96,22 @@ def get(self, request): class UnHandledErrorView(APIView): def get(self, request): - add_breadcrumb( + sentry_sdk.add_breadcrumb( category='URL Endpoints', message='In the unhandled function', level='info', - ) + ) obj = {} obj['keyDoesntExist'] return Response() class CaptureMessageView(APIView): def get(self, request): - add_breadcrumb( + sentry_sdk.add_breadcrumb( category='URL Endpoints', message='In the Capture Message function', level='info', - ) - sentry_sdk.capture_message("You caught me!") + ) + sentry_sdk.capture_message("You caught me!", "fatal") return Response() - - - - - diff --git a/myproject/init.py b/myproject/__init__.py similarity index 100% rename from myproject/init.py rename to myproject/__init__.py diff --git a/myproject/settings.py b/myproject/settings.py index 31fafdf..185fcd1 100644 --- a/myproject/settings.py +++ b/myproject/settings.py @@ -43,29 +43,28 @@ # Import configure and initialize Sentry SDK import sentry_sdk -from sentry_sdk.integrations.django import DjangoIntegration sentry_sdk.init( - dsn="YOUR_DSN", - integrations=[DjangoIntegration()], + dsn="https://d655584d05f14c58b86e9034aab6817f@o447951.ingest.us.sentry.io/5461230", release=os.environ.get("VERSION"), - environment="Production" + environment="Production", + # Set traces_sample_rate to 1.0 to capture 100% of traces. + # We recommend adjusting this value in production. + traces_sample_rate=1.0, ) MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', - 'corsheaders.middleware.CorsPostCsrfMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] #Configuration for 'corsheaders.middleware.CorsMiddleware' CORS_ORIGIN_ALLOW_ALL = True -CORS_REPLACE_HTTPS_REFERER = True from corsheaders.defaults import default_headers diff --git a/requirements.txt b/requirements.txt index 0fe5fb4..768991f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==2.2.3 -sentry-sdk==0.15.1 -djangorestframework==3.10.0 -django-cors-headers==3.0.2 +Django==5.0.7 +sentry-sdk==2.11.0 +djangorestframework==3.15.2 +django-cors-headers==4.4.0 From 4094bd2bb04e60c1dd87984a400647b255cd0a7d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 26 Jul 2024 15:22:48 +0200 Subject: [PATCH 2/2] Formatted with black --- manage.py | 4 +- myapp/apps.py | 2 +- myapp/models.py | 3 +- myapp/urls.py | 15 +++++--- myapp/views.py | 66 +++++++++++++++++--------------- myproject/settings.py | 87 +++++++++++++++++++++---------------------- myproject/urls.py | 3 +- myproject/wsgi.py | 2 +- 8 files changed, 97 insertions(+), 85 deletions(-) diff --git a/manage.py b/manage.py index 6bb3761..95fb3d1 100644 --- a/manage.py +++ b/manage.py @@ -5,7 +5,7 @@ def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +17,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/myapp/apps.py b/myapp/apps.py index 74d6d13..c976c37 100644 --- a/myapp/apps.py +++ b/myapp/apps.py @@ -2,4 +2,4 @@ class MyappConfig(AppConfig): - name = 'myapp' + name = "myapp" diff --git a/myapp/models.py b/myapp/models.py index e018d4e..26890a4 100644 --- a/myapp/models.py +++ b/myapp/models.py @@ -2,8 +2,9 @@ # Create your models here. + class Inventory(models.Model): - name = models.TextField(), + name = models.TextField() count = models.IntegerField() def __str__(self): diff --git a/myapp/urls.py b/myapp/urls.py index c1e346a..805b6e6 100644 --- a/myapp/urls.py +++ b/myapp/urls.py @@ -1,12 +1,17 @@ from django.urls import path, include from rest_framework import routers, serializers, viewsets -from .views import InventoreyView, HandledErrorView, UnHandledErrorView, CaptureMessageView +from .views import ( + InventoreyView, + HandledErrorView, + UnHandledErrorView, + CaptureMessageView, +) urlpatterns = [ - path('checkout', InventoreyView.as_view()), - path('handled', HandledErrorView.as_view()), - path('unhandled', UnHandledErrorView.as_view()), - path('message', CaptureMessageView.as_view()), + path("checkout", InventoreyView.as_view()), + path("handled", HandledErrorView.as_view()), + path("unhandled", UnHandledErrorView.as_view()), + path("message", CaptureMessageView.as_view()), ] diff --git a/myapp/views.py b/myapp/views.py index 601352c..d487e65 100644 --- a/myapp/views.py +++ b/myapp/views.py @@ -12,55 +12,60 @@ InventoryData = [ {"name": "wrench", "count": 1}, {"name": "nails", "count": 1}, - {"name": "hammer", "count": 1}, + {"name": "hammer", "count": 1}, ] def find_in_inventory(itemId): for item in InventoryData: - if item['name'] == itemId: + if item["name"] == itemId: return item raise Exception("Item : " + itemId + " not in inventory ") def process_order(cart): sentry_sdk.add_breadcrumb( - category='Process Order', - message='Step taken to process an order', - level='info', + category="Process Order", + message="Step taken to process an order", + level="info", ) global InventoryData tempInventory = InventoryData for item in cart: - itemID = item['id'] + itemID = item["id"] inventoryItem = find_in_inventory(itemID) - if inventoryItem['count'] <= 0: + if inventoryItem["count"] <= 0: raise Exception("Not enough inventory for " + itemID) else: - inventoryItem['count'] -= 1 - print( 'Success: ' + itemID + ' was purchased, remaining stock is ' + str(inventoryItem['count']) ) + inventoryItem["count"] -= 1 + print( + "Success: " + + itemID + + " was purchased, remaining stock is " + + str(inventoryItem["count"]) + ) InventoryData = tempInventory class SentryContextMixin(object): def dispatch(self, request, *args, **kwargs): - if (request.body): - body_unicode = request.body.decode('utf-8') + if request.body: + body_unicode = request.body.decode("utf-8") order = json.loads(body_unicode) with sentry_sdk.configure_scope() as scope: - scope.user = { "email" : order["email"] } + scope.user = {"email": order["email"]} - transactionId = request.headers.get('X-Transaction-ID') + transactionId = request.headers.get("X-Transaction-ID") global InventoryData with sentry_sdk.configure_scope() as scope: - if(transactionId): + if transactionId: scope.set_tag("transaction_id", transactionId) - if(Inventory): + if Inventory: scope.set_extra("inventory", InventoryData) return super(SentryContextMixin, self).dispatch(request, *args, **kwargs) @@ -72,11 +77,10 @@ def get(self, request): results = InventorySerializer(InventoryData, many=True).data return Response(results) - def post(self, request, format=None): - body_unicode = request.body.decode('utf-8') + body_unicode = request.body.decode("utf-8") order = json.loads(body_unicode) - cart = order['cart'] + cart = order["cart"] process_order(cart) return Response(InventoryData) @@ -84,33 +88,35 @@ def post(self, request, format=None): class HandledErrorView(APIView): def get(self, request): sentry_sdk.add_breadcrumb( - category='URL Endpoints', - message='In the handled function', - level='info', + category="URL Endpoints", + message="In the handled function", + level="info", ) try: - '2' + 2 + "2" + 2 except Exception as err: sentry_sdk.capture_exception(err) return Response() + class UnHandledErrorView(APIView): - def get(self, request): + def get(self, request): sentry_sdk.add_breadcrumb( - category='URL Endpoints', - message='In the unhandled function', - level='info', + category="URL Endpoints", + message="In the unhandled function", + level="info", ) obj = {} - obj['keyDoesntExist'] + obj["keyDoesntExist"] return Response() + class CaptureMessageView(APIView): def get(self, request): sentry_sdk.add_breadcrumb( - category='URL Endpoints', - message='In the Capture Message function', - level='info', + category="URL Endpoints", + message="In the Capture Message function", + level="info", ) sentry_sdk.capture_message("You caught me!", "fatal") diff --git a/myproject/settings.py b/myproject/settings.py index 185fcd1..39e1530 100644 --- a/myproject/settings.py +++ b/myproject/settings.py @@ -20,25 +20,24 @@ # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'h0z^7km9#o2w@eig&z9f-bso6tks@$q*ho#39o&l0z)aux_2t^' +SECRET_KEY = "h0z^7km9#o2w@eig&z9f-bso6tks@$q*ho#39o&l0z)aux_2t^" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['dev.getsentry.net', 'localhost'] +ALLOWED_HOSTS = ["dev.getsentry.net", "localhost"] # Application definition INSTALLED_APPS = [ - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'myapp', - 'rest_framework', - 'corsheaders', + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "myapp", + "rest_framework", + "corsheaders", ] # Import configure and initialize Sentry SDK @@ -48,51 +47,51 @@ dsn="https://d655584d05f14c58b86e9034aab6817f@o447951.ingest.us.sentry.io/5461230", release=os.environ.get("VERSION"), environment="Production", - # Set traces_sample_rate to 1.0 to capture 100% of traces. + # Set traces_sample_rate to 1.0 to capture 100% of traces. # We recommend adjusting this value in production. traces_sample_rate=1.0, ) MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -#Configuration for 'corsheaders.middleware.CorsMiddleware' +# Configuration for 'corsheaders.middleware.CorsMiddleware' CORS_ORIGIN_ALLOW_ALL = True from corsheaders.defaults import default_headers CORS_ALLOW_HEADERS = list(default_headers) + [ - 'X-Transaction-ID', - 'X-Session-ID', + "X-Transaction-ID", + "X-Session-ID", ] -ROOT_URLCONF = 'myproject.urls' +ROOT_URLCONF = "myproject.urls" REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. - 'DEFAULT_AUTHENTICATION_CLASSES': [], - 'DEFAULT_PERMISSION_CLASSES': [], - 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', - 'UNAUTHENTICATED_USER': None + "DEFAULT_AUTHENTICATION_CLASSES": [], + "DEFAULT_PERMISSION_CLASSES": [], + "EXCEPTION_HANDLER": "rest_framework.views.exception_handler", + "UNAUTHENTICATED_USER": None, } TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", #'django.contrib.auth.context_processors.auth', #'django.contrib.messages.context_processors.messages', ], @@ -100,16 +99,16 @@ }, ] -WSGI_APPLICATION = 'myproject.wsgi.application' +WSGI_APPLICATION = "myproject.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -119,16 +118,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -136,9 +135,9 @@ # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -150,4 +149,4 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ -STATIC_URL = '/static/' +STATIC_URL = "/static/" diff --git a/myproject/urls.py b/myproject/urls.py index a20d14c..5a57641 100644 --- a/myproject/urls.py +++ b/myproject/urls.py @@ -13,8 +13,9 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.urls import path, include urlpatterns = [ - path(r'', include('myapp.urls')), + path(r"", include("myapp.urls")), ] diff --git a/myproject/wsgi.py b/myproject/wsgi.py index 8ec7b23..c130bcc 100644 --- a/myproject/wsgi.py +++ b/myproject/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") application = get_wsgi_application()