Author SHA1 Message Date
Emma Eilefsen Glenna d5de4597c3 Fix: "Insufficient Funds" when user has exact amount 2024-02-15 19:36:04 +00:00
38 changed files with 1323 additions and 679 deletions

No files matched your search

-19
View File
@@ -1,19 +0,0 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
+8 -5
View File
@@ -2,27 +2,30 @@ variables:
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip" PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
default: default:
image: nikolaik/python-nodejs:python3.9-nodejs20 image: nikolaik/python-nodejs:python3.8-nodejs16
cache: cache:
paths: paths:
- .cache/pip - .cache/pip
- .venv - .venv
before_script:
- poetry config virtualenvs.in-project true
- poetry install
django-checks: django-checks:
stage: build stage: build
script: script:
- uv run manage.py migrate - poetry run python manage.py migrate
- uv run manage.py check - poetry run python manage.py check
tests: tests:
stage: test stage: test
script: script:
- uv run bin/test - poetry run bin/test
lint: lint:
stage: test stage: test
script: script:
- uv run bin/pre-commit - poetry run bin/lint
deploy: deploy:
stage: deploy stage: deploy
+16 -10
View File
@@ -1,12 +1,18 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: local
rev: 'v0.11.7'
hooks: hooks:
- id: ruff - id: black
args: [--fix, --exit-non-zero-on-fix] name: black
- id: ruff-format entry: black
- repo: https://github.com/adamchainz/django-upgrade args: [--check]
rev: "1.24.0" language: system
hooks: require_serial: true
- id: django-upgrade types: [python]
args: [--target-version, "4.2"] - id: isort
name: isort
entry: isort
args: [--check]
language: system
types: [python]
+7 -3
View File
@@ -4,9 +4,11 @@ Before starting, make sure you have a working python and nodejs setup with pyenv
```bash ```bash
apt-get install libmysqlclient-dev apt-get install libmysqlclient-dev
poetry install
poetry shell
pre-commit install pre-commit install
uv run manage.py migrate python manage.py migrate
uv run bin/run bin/run
cp .env-default .env cp .env-default .env
# in another terminal # in another terminal
@@ -17,7 +19,7 @@ npm start
## References ## References
- We use JS code generation to work with queries/mutations in our graphql API: <https://github.com/dotansimha/graphql-typed-document-node> - We use JS code generation to work with queries/mutations in our graphql API: https://github.com/dotansimha/graphql-typed-document-node
## TODO ## TODO
@@ -26,8 +28,10 @@ npm start
- inventory views - inventory views
- Stats: Implement users tab - Stats: Implement users tab
## TODO-old ## TODO-old
- Larger pictures - Larger pictures
- Compact GUI for max 768 vertical pixels - Compact GUI for max 768 vertical pixels
- Search right of in title - Search right of in title
Regular → Executable
View File
File mode changed.
+2 -9
View File
@@ -1,20 +1,13 @@
from django.contrib import admin from django.contrib import admin
from bbs.models import ( from bbs.models import *
InventoryTransaction,
Order,
OrderLine,
Product,
Transaction,
UserProfile,
)
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
pass pass
admin.site.register(Product, ProductAdmin)
admin.site.register(InventoryTransaction) admin.site.register(InventoryTransaction)
admin.site.register(Order) admin.site.register(Order)
admin.site.register(OrderLine) admin.site.register(OrderLine)
+3 -1
View File
@@ -1,3 +1,5 @@
from typing import List
import strawberry import strawberry
@@ -17,6 +19,6 @@ class FieldError(Error):
@strawberry.type @strawberry.type
class FormErrors: class FormErrors:
fields: list[FieldError] fields: List[FieldError]
message: str = "" message: str = ""
error: bool = True error: bool = True
+9 -14
View File
@@ -1,4 +1,4 @@
from typing import Annotated, Union from typing import List
import strawberry import strawberry
from django.conf import settings from django.conf import settings
@@ -27,18 +27,16 @@ class CreateOrderSuccess:
@strawberry.type @strawberry.type
class InsufficientFunds(Error): # noqa: N818 class InsufficientFunds(Error):
amount_lacking: float amount_lacking: float
CreateOrderResponse = Annotated[ CreateOrderResponse = strawberry.union("CreateOrderResponse", [CreateOrderSuccess, InsufficientFunds, FormErrors])
Union[CreateOrderSuccess, InsufficientFunds, FormErrors], strawberry.union("CreateOrderResponse") # noqa: UP007
]
@strawberry.mutation @strawberry.mutation
@transaction.atomic @transaction.atomic
def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external: bool = False) -> CreateOrderResponse: # noqa: FBT001 def create_order(customer_id: ID, order_lines: List[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
# clean and validate # clean and validate
try: try:
user = UserModel.objects.get(pk=customer_id) user = UserModel.objects.get(pk=customer_id)
@@ -68,10 +66,9 @@ def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external
product.inventory_amount -= ol.amount product.inventory_amount -= ol.amount
product.save() product.save()
if not is_external and user.profile.balance <= order_sum: if not is_external and user.profile.balance < order_sum:
return InsufficientFunds( return InsufficientFunds(
message="Du har ikke nok penger på bok.", message="Du har ikke nok penger på bok.", amount_lacking=abs(order_sum - user.profile.balance)
amount_lacking=abs(order_sum - user.profile.balance),
) )
# create # create
@@ -94,14 +91,12 @@ class CreateDepositSuccess:
@strawberry.type @strawberry.type
class AboveMaxSaldo(Error): # noqa: N818 class AboveMaxSaldo(Error):
max_saldo: float max_saldo: float
above: float above: float
CreateDepositResponse = Annotated[ CreateDepositResponse = strawberry.union("CreateDepositResponse", [CreateDepositSuccess, AboveMaxSaldo, FormErrors])
Union[CreateDepositSuccess, AboveMaxSaldo, FormErrors], strawberry.union("CreateDepositResponse") # noqa: UP007
]
@strawberry.mutation @strawberry.mutation
@@ -123,7 +118,7 @@ def create_deposit(user_id: ID, amount: int) -> CreateDepositResponse:
fields=[FieldError(field="amount", message=message)], fields=[FieldError(field="amount", message=message)],
) )
# 1337 is special <3 # 1337 is special <3
if amount + user.profile.balance > settings.BBS_SALDO_MAX and amount + user.profile.balance != 1337: # noqa: PLR2004 if amount + user.profile.balance > settings.BBS_SALDO_MAX and amount + user.profile.balance != 1337:
return AboveMaxSaldo( return AboveMaxSaldo(
message="Du prøver å sette inn for mye penger", message="Du prøver å sette inn for mye penger",
max_saldo=settings.BBS_SALDO_MAX, max_saldo=settings.BBS_SALDO_MAX,
+6 -7
View File
@@ -1,12 +1,14 @@
import strawberry import strawberry
import strawberry_django import strawberry_django
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth import get_user_model
from django.db import transaction from django.db import transaction
from strawberry import auto from strawberry_django import auto
from bbs import models from bbs import models
from bbs.api.types.users import User from bbs.api.types.users import User
DjangoUser = get_user_model()
@strawberry_django.input(DjangoUser) @strawberry_django.input(DjangoUser)
class UserInput: class UserInput:
@@ -17,12 +19,9 @@ class UserInput:
@strawberry.mutation @strawberry.mutation
@transaction.atomic @transaction.atomic
def register_mutation(input: UserInput) -> User: # noqa: A002 def register_mutation(input: UserInput) -> User:
user = DjangoUser.objects.create_user( user = DjangoUser.objects.create_user(
first_name=input.first_name, first_name=input.first_name, last_name=input.last_name, email=input.email, username=input.email
last_name=input.last_name,
email=input.email,
username=input.email,
) )
models.UserProfile.objects.create(user=user) models.UserProfile.objects.create(user=user)
return user return user
+3 -1
View File
@@ -1,3 +1,5 @@
from typing import List
import strawberry import strawberry
import strawberry_django import strawberry_django
@@ -6,4 +8,4 @@ from bbs.api.types.inventory import Transaction, TransactionOrdering
@strawberry.type @strawberry.type
class InventoryQueries: class InventoryQueries:
transaction_list: list[Transaction] = strawberry_django.field(pagination=True, ordering=TransactionOrdering) transaction_list: List[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
+8 -17
View File
@@ -1,23 +1,17 @@
from typing import List
import strawberry import strawberry
import strawberry_django import strawberry_django
from bbs import models from bbs import models
from bbs.api.types.shop import ( from bbs.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
Order,
OrderOrdering,
OrderStats,
OrderStatsByTime,
Point,
Product,
ProductOrderStat,
)
@strawberry.type @strawberry.type
class ShopQueries: class ShopQueries:
all_products: list[Product] = strawberry_django.field() all_products: List[Product] = strawberry_django.field()
all_orders: list[Order] = strawberry_django.field() all_orders: List[Order] = strawberry_django.field()
order_list: list[Order] = strawberry_django.field(pagination=True, ordering=OrderOrdering) order_list: List[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
@strawberry.field @strawberry.field
def order_stats(self) -> OrderStats: def order_stats(self) -> OrderStats:
@@ -32,11 +26,8 @@ class ShopQueries:
) )
@strawberry.field @strawberry.field
def product_stats(self) -> list[ProductOrderStat]: def product_stats(self) -> List[ProductOrderStat]:
return [ return [
ProductOrderStat( ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
product_name=stat["product_name"],
data=[Point(**p) for p in stat["data"]],
)
for stat in models.OrderLine.objects.product_order_stats().as_series() for stat in models.OrderLine.objects.product_order_stats().as_series()
] ]
+3 -1
View File
@@ -1,3 +1,5 @@
from typing import List
import strawberry import strawberry
import strawberry_django import strawberry_django
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth.models import User as DjangoUser
@@ -8,7 +10,7 @@ from bbs.api.types.users import User
@strawberry.type @strawberry.type
class UsersQueries: class UsersQueries:
all_users: list[User] = strawberry_django.field() all_users: List[User] = strawberry_django.field()
@strawberry.field @strawberry.field
def user(self, user_id: ID) -> User: def user(self, user_id: ID) -> User:
+2 -2
View File
@@ -1,6 +1,6 @@
import strawberry import strawberry
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from strawberry.extensions import SchemaExtension from strawberry.extensions import Extension
from bbs.api.mutations.shop import ShopMutations from bbs.api.mutations.shop import ShopMutations
from bbs.api.mutations.users import UsersMutations from bbs.api.mutations.users import UsersMutations
@@ -19,7 +19,7 @@ class Mutation(ShopMutations, UsersMutations):
pass pass
class HandleNotFound(SchemaExtension): class HandleNotFound(Extension):
def on_request_end(self): def on_request_end(self):
result = self.execution_context.result result = self.execution_context.result
if result.errors: if result.errors:
+4 -9
View File
@@ -1,16 +1,11 @@
from typing import TYPE_CHECKING, Annotated
import strawberry
import strawberry_django import strawberry_django
from strawberry import auto from strawberry_django import auto
from bbs import models from bbs import models
from bbs.api.types.users import User
if TYPE_CHECKING:
from .users import User
@strawberry_django.order_type(models.Transaction) @strawberry_django.ordering.order(models.Transaction)
class TransactionOrdering: class TransactionOrdering:
created: auto created: auto
@@ -18,6 +13,6 @@ class TransactionOrdering:
@strawberry_django.type(models.Transaction) @strawberry_django.type(models.Transaction)
class Transaction: class Transaction:
id: auto id: auto
user: Annotated["User", strawberry.lazy(".users")] user: "User"
amount: auto amount: auto
created: auto created: auto
+11 -14
View File
@@ -1,17 +1,14 @@
from typing import TYPE_CHECKING, Annotated from typing import List
import strawberry import strawberry
import strawberry_django import strawberry_django
from strawberry import auto from strawberry_django import auto
from bbs import models from bbs import models
from bbs.api.types.utils import JSONScalar from bbs.api.types.utils import JSONScalar
if TYPE_CHECKING:
from .users import User
@strawberry_django.filters.filter(models.Product)
@strawberry_django.filter_type(models.Product)
class ProductFilter: class ProductFilter:
id: auto id: auto
active: auto active: auto
@@ -28,7 +25,7 @@ class Product:
user_counts: JSONScalar user_counts: JSONScalar
@strawberry_django.order_type(models.Order) @strawberry_django.ordering.order(models.Order)
class OrderOrdering: class OrderOrdering:
created: auto created: auto
@@ -36,10 +33,10 @@ class OrderOrdering:
@strawberry_django.type(models.Order) @strawberry_django.type(models.Order)
class Order: class Order:
id: auto id: auto
customer: Annotated["User", strawberry.lazy(".users")] customer: strawberry.LazyType["User", "bbs.api.types.users"]
order_sum: auto order_sum: auto
created: auto created: auto
orderlines: list["OrderLine"] orderlines: List["OrderLine"]
is_external: bool is_external: bool
@@ -60,10 +57,10 @@ class OrderStatsByTime:
@strawberry.type @strawberry.type
class OrderStats: class OrderStats:
yearly: list[OrderStatsByTime] yearly: List[OrderStatsByTime]
monthly: list[OrderStatsByTime] monthly: List[OrderStatsByTime]
daily: list[OrderStatsByTime] daily: List[OrderStatsByTime]
hourly: list[OrderStatsByTime] hourly: List[OrderStatsByTime]
@strawberry.type @strawberry.type
@@ -75,4 +72,4 @@ class Point:
@strawberry.type @strawberry.type
class ProductOrderStat: class ProductOrderStat:
product_name: str product_name: str
data: list[Point] data: List[Point]
+9 -9
View File
@@ -1,14 +1,17 @@
from datetime import datetime from datetime import datetime
from typing import List
from decimal import Decimal from decimal import Decimal
import strawberry import strawberry
import strawberry_django import strawberry_django
from django.contrib.auth.models import User as DjangoUser from django.contrib.auth import get_user_model
from strawberry import auto from strawberry_django import auto
from bbs import models from bbs import models
from bbs.api.types.shop import Point, ProductOrderStat from bbs.api.types.shop import Point, ProductOrderStat
UserModel = get_user_model()
@strawberry.type @strawberry.type
class TopMonth: class TopMonth:
@@ -26,21 +29,18 @@ class UserProfile:
order_sum_total: Decimal order_sum_total: Decimal
@strawberry.field() @strawberry.field()
def top_months(self) -> list[TopMonth]: def top_months(self) -> List[TopMonth]:
return [TopMonth(**month) for month in self.top_months()] return [TopMonth(**month) for month in self.top_months()]
@strawberry.field() @strawberry.field()
def product_order_stats(self) -> list[ProductOrderStat]: def product_order_stats(self) -> List[ProductOrderStat]:
return [ return [
ProductOrderStat( ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
product_name=stat["product_name"],
data=[Point(**p) for p in stat["data"]],
)
for stat in self.product_order_stats().as_series() for stat in self.product_order_stats().as_series()
] ]
@strawberry_django.type(DjangoUser) @strawberry_django.type(UserModel)
class User: class User:
id: auto id: auto
first_name: auto first_name: auto
+2 -8
View File
@@ -5,18 +5,12 @@ from bbs.models import InventoryTransaction, Product
MAX_DIGITS = 9 MAX_DIGITS = 9
DECIMAL_PLACES = 1 DECIMAL_PLACES = 1
class InventoryTransactionForm(forms.ModelForm): class InventoryTransactionForm(forms.ModelForm):
unit_price = forms.DecimalField( unit_price = forms.DecimalField(max_digits=MAX_DIGITS, decimal_places=DECIMAL_PLACES, required=True, localize=True)
max_digits=MAX_DIGITS,
decimal_places=DECIMAL_PLACES,
required=True,
localize=True,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Only show active products # Only show active products
super().__init__(*args, **kwargs) super(InventoryTransactionForm, self).__init__(*args, **kwargs)
self.fields["product"].queryset = Product.objects.filter(active=True) self.fields["product"].queryset = Product.objects.filter(active=True)
class Meta: class Meta:
+2 -2
View File
@@ -13,11 +13,11 @@ class Command(BaseCommand):
def add_arguments(self, parser: CommandParser): def add_arguments(self, parser: CommandParser):
parser.add_argument("--filename", default="schema.graphql", help="`-` prints to stdout") parser.add_argument("--filename", default="schema.graphql", help="`-` prints to stdout")
def handle(self, *args, filename=None, **options): # noqa: ARG002 def handle(self, *args, filename=None, **options):
schema_output = print_schema(schema_symbol) schema_output = print_schema(schema_symbol)
if filename != "-": if filename != "-":
with Path(settings.BASE_DIR, filename).open("w+") as fp: with Path(settings.BASE_DIR, filename).open("w+") as fp:
fp.write(schema_output) fp.write(schema_output)
self.stdout.write(self.style.SUCCESS("OK")) self.stdout.write(self.style.SUCCESS("OK"))
else: else:
pass print(schema_output)
+4 -3
View File
@@ -10,10 +10,11 @@ class Command(BaseCommand):
parser.add_argument("--dry-run", action="store_true") parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--force", action="store_true") parser.add_argument("--force", action="store_true")
def handle(self, *args, dry_run=False, force=False, **options): # noqa: ARG002 def handle(self, *args, dry_run=False, force=False, **options):
for profile in UserProfile.objects.all().order_by("id"): for profile in UserProfile.objects.all().order_by("id"):
if dry_run: if dry_run:
pass print(profile.user.email, profile.image_url())
elif force or not profile.image: else:
if force or not profile.image:
profile.image = profile.image_url() profile.image = profile.image_url()
profile.save() profile.save()
+2 -1
View File
@@ -3,6 +3,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
@@ -179,6 +180,6 @@ class Migration(migrations.Migration):
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name="orderline", name="orderline",
unique_together={("order", "product")}, unique_together=set([("order", "product")]),
), ),
] ]
@@ -4,6 +4,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("bbs", "0001_initial"), ("bbs", "0001_initial"),
] ]
+5 -4
View File
@@ -4,14 +4,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("bbs", "0002_alter_inventorytransaction_amount_and_more"), ('bbs', '0002_alter_inventorytransaction_amount_and_more'),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name="order", model_name='order',
name="order_sum", name='order_sum',
field=models.DecimalField(db_column="sum", decimal_places=1, max_digits=9), field=models.DecimalField(db_column='sum', decimal_places=1, max_digits=9),
), ),
] ]
+18 -21
View File
@@ -1,7 +1,8 @@
import hashlib # noqa: EXE002 import hashlib
from datetime import datetime from datetime import datetime
from decimal import Decimal
from itertools import groupby from itertools import groupby
from typing import Type
from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
@@ -86,26 +87,21 @@ class Product(models.Model):
return _format_counts(per_user) return _format_counts(per_user)
def __str__(self): # noqa: DJ012 def __str__(self):
return self.name return self.name
class Meta: # noqa: DJ012 class Meta:
ordering = ["name"] ordering = ["name"]
class OrderManager(models.Manager): class OrderManager(models.Manager):
def count_by_created(self, extract_class: type[Extract]): def count_by_created(self, extract_class: Type[Extract]):
period = extract_class("created") period = extract_class("created")
return self.get_queryset().annotate(period=period).values("period").annotate(count=Count("id")) return self.get_queryset().annotate(period=period).values("period").annotate(count=Count("id"))
def count_by_created_period(self, period: str): def count_by_created_period(self, period: str):
periods = { periods = {"yearly": ExtractYear, "monthly": ExtractMonth, "daily": ExtractIsoWeekDay, "hourly": ExtractHour}
"yearly": ExtractYear, assert period in periods
"monthly": ExtractMonth,
"daily": ExtractIsoWeekDay,
"hourly": ExtractHour,
}
assert period in periods # noqa: S101
return self.count_by_created(periods[period]) return self.count_by_created(periods[period])
@@ -128,7 +124,7 @@ class Order(models.Model):
def reciept(self): def reciept(self):
return ", ".join([str(ol) for ol in self.orderlines.all()]) return ", ".join([str(ol) for ol in self.orderlines.all()])
def __str__(self): # noqa: DJ012 def __str__(self):
return f"{self.customer or EXTERNAL_USER}: {self.order_sum} kr" return f"{self.customer or EXTERNAL_USER}: {self.order_sum} kr"
@@ -167,10 +163,10 @@ class OrderLine(models.Model):
def price(self): def price(self):
return self.amount * self.unit_price return self.amount * self.unit_price
def __str__(self): # noqa: DJ012 def __str__(self):
return f"{self.amount} {self.product} ({self.unit_price} kr per)" return f"{self.amount} {self.product} ({self.unit_price} kr per)"
class Meta: # noqa: DJ012 class Meta:
unique_together = ("order", "product") unique_together = ("order", "product")
@@ -182,7 +178,7 @@ class Transaction(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
return f"{self.id}: {self.user}: {self.amount}" return f"{self.id}: { self.user}: {self.amount}"
class InventoryTransaction(models.Model): class InventoryTransaction(models.Model):
@@ -210,7 +206,7 @@ class InventoryTransaction(models.Model):
def price(self): def price(self):
return self.amount * self.unit_price return self.amount * self.unit_price
def __str__(self): # noqa: DJ012 def __str__(self):
return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)" return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)"
@@ -224,7 +220,8 @@ class UserProfile(models.Model):
def last_purchase_date(self) -> datetime: def last_purchase_date(self) -> datetime:
last = self.last_purchase() last = self.last_purchase()
return last.created if last else datetime.min # noqa: DTZ901 last_p = last.created if last else datetime.min
return last_p
def top_months(self, limit=5): def top_months(self, limit=5):
return ( return (
@@ -243,16 +240,16 @@ class UserProfile(models.Model):
def image_url(self) -> str: def image_url(self) -> str:
if self.user.email: if self.user.email:
md5_hash = hashlib.md5(self.user.email.lower().strip().encode()).hexdigest() # noqa: S324 md5_hash = hashlib.md5(self.user.email.lower().strip().encode()).hexdigest()
return f"https://www.gravatar.com/avatar/{md5_hash}?d=identicon" return f"https://www.gravatar.com/avatar/{md5_hash}?d=identicon"
return "" return ""
def save(self, **kwargs): # noqa: DJ012 def save(self, **kwargs):
if not self.image: if not self.image:
self.image = self.image_url() self.image = self.image_url()
super().save(**kwargs) super().save(**kwargs)
def __str__(self): # noqa: DJ012 def __str__(self):
return f"{self.user}" return f"{self.user}"
+14 -13
View File
@@ -1,13 +1,12 @@
# Django settings for bbs project. # noqa: EXE002 # Django settings for bbs project.
import contextlib
import os import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # noqa: PTH120 BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = True DEBUG = True
ADMINS = ( ADMINS = (
# ('Your Name', 'your_email@example.com'), # noqa: ERA001 # ('Your Name', 'your_email@example.com'),
) )
MANAGERS = ADMINS MANAGERS = ADMINS
@@ -21,18 +20,18 @@ DATABASES = {
TIME_ZONE = "Europe/Oslo" TIME_ZONE = "Europe/Oslo"
LANGUAGE_CODE = "nb" LANGUAGE_CODE = "nb"
USE_I18N = True USE_I18N = True
USE_TZ = True USE_L10N = True
SITE_ID = 1 SITE_ID = 1
# Media and uploads # Media and uploads
MEDIA_ROOT = os.path.join(BASE_DIR, "media") # noqa: PTH118 MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
# Static files # Static files
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") # noqa: PTH118 STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
STATIC_URL = "/static/" STATIC_URL = "/static/"
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),) # noqa: PTH118 STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.FileSystemFinder",
@@ -40,13 +39,13 @@ STATICFILES_FINDERS = (
) )
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
SECRET_KEY = "osv$uy-ea$*%lc)+xiwt5@2b7au%u*c!pa6hmh#vl)*+tp%fss" # noqa: S105 SECRET_KEY = "osv$uy-ea$*%lc)+xiwt5@2b7au%u*c!pa6hmh#vl)*+tp%fss"
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [ "DIRS": [
os.path.join(BASE_DIR, "templates"), # noqa: PTH118 os.path.join(BASE_DIR, "templates"),
], ],
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
@@ -98,7 +97,7 @@ LOGIN_REDIRECT_URL = "/"
SESSION_COOKIE_AGE = 1209600 # 15 minutes SESSION_COOKIE_AGE = 1209600 # 15 minutes
SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True
BBS_SALDO_MAX = 1000 BBS_SALDO_MAX = 600
BBS_LIMIT_DEPOSITS = False BBS_LIMIT_DEPOSITS = False
# Thumbnails # Thumbnails
@@ -142,5 +141,7 @@ CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", "http://localhost:3000",
] ]
with contextlib.suppress(ImportError): try:
from .local_settings import * # noqa: F403 from .local_settings import *
except ImportError:
pass
+4 -8
View File
@@ -1,4 +1,4 @@
import pytest # noqa: INP001 import pytest
from django.test import Client from django.test import Client
from strawberry.django.test import GraphQLTestClient from strawberry.django.test import GraphQLTestClient
@@ -7,21 +7,17 @@ from bbs.models import Order, Product
@pytest.fixture(name="gql_client") @pytest.fixture(name="gql_client")
def gql_client_fixture(client: Client): def gql_client_fixture(client: Client):
return GraphQLTestClient(client) yield GraphQLTestClient(client)
@pytest.fixture(name="product") @pytest.fixture(name="product")
def product_fix(db): # noqa: ARG001 def product_fix(db):
return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30) return Product.objects.create(name="borg", sale_price_ext=35, sale_price_int=30)
def test_create_order(product, gql_client): def test_create_order(product, gql_client):
assert Order.objects.count() == 0 assert Order.objects.count() == 0
variables = { variables = dict(customerId="external", isExternal=True, orderLines=[{"productId": product.id, "amount": 1}])
"customerId": "external",
"isExternal": True,
"orderLines": [{"productId": product.id, "amount": 1}],
}
query = """ query = """
mutation CreateOrder($customerId: ID!, $isExternal: Boolean!, $orderLines: [OrderLineInput!]!) { mutation CreateOrder($customerId: ID!, $isExternal: Boolean!, $orderLines: [OrderLineInput!]!) {
createOrder(customerId: $customerId, isExternal: $isExternal, orderLines: $orderLines) { createOrder(customerId: $customerId, isExternal: $isExternal, orderLines: $orderLines) {
+6 -11
View File
@@ -2,8 +2,7 @@ from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.views import LoginView, LogoutView from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path from django.urls import path, re_path
from django.views.decorators.csrf import csrf_exempt
from strawberry.django.views import GraphQLView from strawberry.django.views import GraphQLView
from bbs.api.schema import schema from bbs.api.schema import schema
@@ -13,17 +12,13 @@ admin.autodiscover()
urlpatterns = [ urlpatterns = [
# TODO: migrate reports to SPA # TODO: migrate reports to SPA
path("inventory/", inventory, name="inventory"), re_path(r"^inventory/$", inventory, name="inventory"),
path("inventory/add/", inventory_add, name="inventory-add"), re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
path("report", report, name="report"), re_path(r"^report$", report, name="report"),
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"), path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
path( path("accounts/logout/", LogoutView.as_view(), name="django.contrib.auth.views.logout"),
"accounts/logout/",
LogoutView.as_view(),
name="django.contrib.auth.views.logout",
),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("graphql/", csrf_exempt(GraphQLView.as_view(schema=schema)), name="graphql"), path("graphql/", GraphQLView.as_view(schema=schema), name="graphql"),
] ]
urlpatterns += static("/media", document_root=settings.MEDIA_ROOT) urlpatterns += static("/media", document_root=settings.MEDIA_ROOT)
+14 -13
View File
@@ -10,12 +10,12 @@ from bbs.models import InventoryTransaction, Order, Product, Transaction, UserPr
def report(request): def report(request):
# FIXME: split this up in smaller pieces # FIXME: split this up in smaller pieces
datetime_now = datetime.now() # noqa: DTZ005 datetime_now = datetime.now()
start_time = datetime.now() - timedelta(days=30) # noqa: DTZ005 start_time = datetime.now() - timedelta(days=30)
# start of day, 1 month # start of day, 1 month
start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0) start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
# end of day # end of day
end_time = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999) # noqa: DTZ005 end_time = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999)
if request.GET.get("start_time", False) or request.GET.get("end_time", False): if request.GET.get("start_time", False) or request.GET.get("end_time", False):
form = DateRangeForm(request.GET) form = DateRangeForm(request.GET)
@@ -50,14 +50,14 @@ def report(request):
k: { k: {
"name": k, "name": k,
"transactions": group, "transactions": group,
"transactions_sum": sum(x["transactions__price"] for x in group), "transactions_sum": sum(map(lambda x: x["transactions__price"], group)),
"transactions_units": sum(x["transactions__amount"] for x in group), "transactions_units": sum(map(lambda x: x["transactions__amount"], group)),
} }
} }
) )
itrans = InventoryTransaction.objects.filter(created__range=[start_time, end_time]) itrans = InventoryTransaction.objects.filter(created__range=[start_time, end_time])
inv_in = sum(x[1].get("transactions_sum", 0) for x in inventory_products.items()) inv_in = sum(map(lambda x: x[1].get("transactions_sum", 0), inventory_products.items()))
# out # out
out_products = ( out_products = (
@@ -75,8 +75,8 @@ def report(request):
product = { product = {
"name": k, "name": k,
"orderlines": group, "orderlines": group,
"orderlines_sum": sum(x["orderlines__price"] for x in group), "orderlines_sum": sum(map(lambda x: x["orderlines__price"], group)),
"orderlines_units": sum(x["orderlines__amount"] for x in group), "orderlines_units": sum(map(lambda x: x["orderlines__amount"], group)),
} }
if k in inventory_products: if k in inventory_products:
# add diff # add diff
@@ -88,7 +88,7 @@ def report(request):
p[1]["value_diff"] = p[1].get("transactions_sum", 0) - p[1].get("orderlines_sum", 0) p[1]["value_diff"] = p[1].get("transactions_sum", 0) - p[1].get("orderlines_sum", 0)
p[1]["units_diff"] = p[1].get("transactions_units", 0) - p[1].get("orderlines_units", 0) p[1]["units_diff"] = p[1].get("transactions_units", 0) - p[1].get("orderlines_units", 0)
inv_out = sum(x[1].get("orderlines_sum", 0) for x in inventory_products.items()) inv_out = sum(map(lambda x: x[1].get("orderlines_sum", 0), inventory_products.items()))
inv_diff = inv_in - inv_out inv_diff = inv_in - inv_out
@@ -110,7 +110,7 @@ def report(request):
purchases_diff = purchases_end - purchases_start purchases_diff = purchases_end - purchases_start
saldo_diff = saldo_end - saldo_start saldo_diff = saldo_end - saldo_start
return render(request, "report.html", locals()) # noqa: DJ003 return render(request, "report.html", locals())
def inventory(request): def inventory(request):
@@ -120,7 +120,7 @@ def inventory(request):
wholesale_value = sum([p.wholesale_value for p in products_active]) wholesale_value = sum([p.wholesale_value for p in products_active])
num_products = sum(products_active.values_list("inventory_amount", flat=True)) num_products = sum(products_active.values_list("inventory_amount", flat=True))
return render(request, "inventory.html", locals()) # noqa: DJ003 return render(request, "inventory.html", locals())
def inventory_add(request): def inventory_add(request):
@@ -133,12 +133,13 @@ def inventory_add(request):
itrans.user = request.user itrans.user = request.user
itrans.product.save() itrans.product.save()
itrans.save() itrans.save()
messages.success(request, f"Satt inn {itrans.amount} stk {itrans.product}.") messages.success(request, "Satt inn {0} stk {1}.".format(itrans.amount, itrans.product))
return redirect("inventory-add") return redirect("inventory-add")
else:
messages.error(request, "Feil med skjemaet, se under.") messages.error(request, "Feil med skjemaet, se under.")
form = InventoryTransactionForm(data=request.POST) form = InventoryTransactionForm(data=request.POST)
else: else:
form = InventoryTransactionForm() form = InventoryTransactionForm()
return render(request, "inventory_add.html", locals()) # noqa: DJ003 return render(request, "inventory_add.html", locals())
+3 -5
View File
@@ -1,8 +1,6 @@
#!/bin/bash #!/bin/bash
set -eu set -eu
source "$HOME/.local/bin/env"
export VITE_API_URL="https://bbs.neuf.no/api" export VITE_API_URL="https://bbs.neuf.no/api"
PROJECT_PATH=/var/www/neuf.no/bbs PROJECT_PATH=/var/www/neuf.no/bbs
PROCESS_NAME="bbs.neuf.no" PROCESS_NAME="bbs.neuf.no"
@@ -12,9 +10,9 @@ cd "$PROJECT_PATH" || exit 1
git pull git pull
# Backend # Backend
uv sync poetry install
umask 022; uv run manage.py collectstatic --noinput # Collect static umask 022; poetry run python manage.py collectstatic --noinput # Collect static
uv run manage.py migrate # Run DB migrations poetry run python manage.py migrate # Run DB migrations
# Frontend # Frontend
bin/build-frontend bin/build-frontend
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
SRC_DIR="bbs"
black $SRC_DIR
isort $SRC_DIR
Executable
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
SRC_DIR="bbs"
black --check $SRC_DIR
isort --diff $SRC_DIR
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
uvx pre-commit run --all-files
+1 -1
View File
@@ -1 +1 @@
v20 v16
+2 -1
View File
@@ -52,7 +52,8 @@
"vite-plugin-svgr": "^1.1.0" "vite-plugin-svgr": "^1.1.0"
}, },
"engines": { "engines": {
"node": "20" "node": ">=16",
"npm": ">=7"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
+2 -1
View File
@@ -60,6 +60,7 @@
"vite-plugin-svgr": "^1.1.0" "vite-plugin-svgr": "^1.1.0"
}, },
"engines": { "engines": {
"node": "20" "node": ">=16",
"npm": ">=7"
} }
} }
-3
View File
@@ -1,3 +0,0 @@
[tools]
python = "3.9"
uv = "latest"
Generated
+1103
View File
File diff suppressed because it is too large. Load diff
+37 -49
View File
@@ -1,55 +1,43 @@
[project]
name = "bbs"
version = "0.1.0"
description = ""
authors = [{ name = "Nikolai R Kristiansen", email = "nikolaik@gmail.com" }]
requires-python = ">=3.9,<3.10"
license = "MIT"
dependencies = [
"django-cors-headers>=3.10.0,<4",
"django-extensions>=3.1.3,<4",
"django-rename-app>=0.1.3,<0.2",
"Django==4.2.*",
"easy-thumbnails>=2.8.1,<3",
"gunicorn>=20.1.0,<21",
"mysqlclient>=2.0.3,<3",
"Pillow>=9.0.0,<10",
"sentry-sdk>=1.5.5,<2",
"strawberry-graphql-django==0.60.*",
"strawberry-graphql==0.270.*",
]
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "bbs.settings" DJANGO_SETTINGS_MODULE = "bbs.settings"
[dependency-groups] [tool.black]
dev = [
"ruff",
"pytest-django",
"pytest",
]
[tool.ruff]
line-length = 120 line-length = 120
target-version = "py310" target_version = ['py39']
[tool.ruff.lint] [tool.isort]
select = ["ALL"] profile = "black"
ignore = [ line_length = 120
"D", # pydocstyle: Allow undocumented code
"ANN", # flake8-annotations: Allow untyped code
"TD", "FIX", # Allow TODOs and FIXMEs
"RUF012", # FIXME: Fix later https://docs.astral.sh/ruff/rules/mutable-class-default/
"FBT002", # Allow boolean args
"G004", # Allow logging using f-string
"EM101", "EM102", # Allow string in exceptions (prioritice code readability vs exception time readability)
"TRY003", # Allow long exception messages
"E501", # Allow long lines
"COM812", "ISC001", # Avoid conflicting with ruff formatter
]
[tool.ruff.lint.per-file-ignores] [tool.poetry]
# Testing code can deviate a bit more name = "bbs"
"bbs/tests/*.py" = ["S101", "S106", "PLR2004"] version = "0.1.0"
"**/test_*.py" = ["S101", "S106", "PLR2004"] description = ""
"**/tests.py" = ["S101", "S106", "PLR2004"] authors = ["Nikolai R Kristiansen <nikolaik@gmail.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~3.8"
django-extensions = "^3.1.3"
Django = "^4.0.0"
easy-thumbnails = "^2.8.1"
gunicorn = "^20.1.0"
mysqlclient = "^2.0.3"
Pillow = "^9.0.0"
sentry-sdk = "^1.5.5"
strawberry-graphql = "^0.97.0"
strawberry-graphql-django = "^0.2.5"
django-cors-headers = "^3.10.0"
django-rename-app = "^0.1.3"
[tool.poetry.dev-dependencies]
pytest = "*"
black = "*"
pytest-django = "*"
pre-commit = "*"
isort = "*"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Generated
-411
View File
@@ -1,411 +0,0 @@
version = 1
revision = 2
requires-python = "==3.9.*"
[[package]]
name = "asgiref"
version = "3.8.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186, upload-time = "2024-03-22T14:39:36.863Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" },
]
[[package]]
name = "bbs"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "django" },
{ name = "django-cors-headers" },
{ name = "django-extensions" },
{ name = "django-rename-app" },
{ name = "easy-thumbnails" },
{ name = "gunicorn" },
{ name = "mysqlclient" },
{ name = "pillow" },
{ name = "sentry-sdk" },
{ name = "strawberry-graphql" },
{ name = "strawberry-graphql-django" },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
{ name = "pytest-django" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "django", specifier = "==4.2.*" },
{ name = "django-cors-headers", specifier = ">=3.10.0,<4" },
{ name = "django-extensions", specifier = ">=3.1.3,<4" },
{ name = "django-rename-app", specifier = ">=0.1.3,<0.2" },
{ name = "easy-thumbnails", specifier = ">=2.8.1,<3" },
{ name = "gunicorn", specifier = ">=20.1.0,<21" },
{ name = "mysqlclient", specifier = ">=2.0.3,<3" },
{ name = "pillow", specifier = ">=9.0.0,<10" },
{ name = "sentry-sdk", specifier = ">=1.5.5,<2" },
{ name = "strawberry-graphql", specifier = "==0.270.*" },
{ name = "strawberry-graphql-django", specifier = "==0.60.*" },
]
[package.metadata.requires-dev]
dev = [
{ name = "pytest" },
{ name = "pytest-django" },
{ name = "ruff" },
]
[[package]]
name = "certifi"
version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "django"
version = "4.2.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c1/bb/2fad5edc1af2945cb499a2e322ac28e4714fc310bd5201ed1f5a9f73a342/django-4.2.21.tar.gz", hash = "sha256:b54ac28d6aa964fc7c2f7335138a54d78980232011e0cd2231d04eed393dcb0d", size = 10424638, upload-time = "2025-05-07T14:07:07.992Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/4f/aeaa3098da18b625ed672f3da6d1cd94e188d1b2cc27c2c841b2f9666282/django-4.2.21-py3-none-any.whl", hash = "sha256:1d658c7bf5d31c7d0cac1cab58bc1f822df89255080fec81909256c30e6180b3", size = 7993839, upload-time = "2025-05-07T14:07:01.318Z" },
]
[[package]]
name = "django-cors-headers"
version = "3.14.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bc/f4/8b8c3d5e9a0aeea43576fbe623599052a7699abb54378ddb44adb1ef1ed3/django_cors_headers-3.14.0.tar.gz", hash = "sha256:5fbd58a6fb4119d975754b2bc090f35ec160a8373f276612c675b00e8a138739", size = 24892, upload-time = "2023-02-25T07:20:18.41Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/f1/972b3a183192841b811e4b7496de0a31c09ae1d83a5391743c8ae0cbd86e/django_cors_headers-3.14.0-py3-none-any.whl", hash = "sha256:684180013cc7277bdd8702b80a3c5a4b3fcae4abb2bf134dceb9f5dfe300228e", size = 13187, upload-time = "2023-02-25T07:20:16.376Z" },
]
[[package]]
name = "django-extensions"
version = "3.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8a/f1/318684c9466968bf9a9c221663128206e460c1a67f595055be4b284cde8a/django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a", size = 277216, upload-time = "2023-06-05T17:09:01.447Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/7e/ba12b9660642663f5273141018d2bec0a1cae1711f4f6d1093920e157946/django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401", size = 229868, upload-time = "2023-06-05T17:08:58.197Z" },
]
[[package]]
name = "django-rename-app"
version = "0.1.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/f6/0cb0c7169584e8f87c229e6336ce98f90cbb911ae374e2a7683c25e872b4/django_rename_app-0.1.7.tar.gz", hash = "sha256:f1236dacfbaf06d63aa53a665333c9e0b4943ef41019aae7626dd5fb5ffa69b5", size = 8291, upload-time = "2023-11-08T08:22:05.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/4b/6a15f0a25003f7c563218722ee02968f008bedbc4e9d0c08fe9344421d5c/django_rename_app-0.1.7-py2.py3-none-any.whl", hash = "sha256:68ed0dbc9fc167b72cf2f3ba6b464d7981a42a0e09070476cf94f1f603f7a433", size = 8455, upload-time = "2023-11-08T08:22:03.782Z" },
]
[[package]]
name = "easy-thumbnails"
version = "2.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "pillow" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/3b/5fe5c2c20cb08a34759cbd1ca9b8394b121785ebcb033c010a4b27b65a7a/easy_thumbnails-2.10.tar.gz", hash = "sha256:d009462fcd791edf2496e5ed46d044712e1a06b7d4600e4cf2812002e9904ef5", size = 80721, upload-time = "2024-09-12T12:41:00.361Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/ef/de1f3a7421d7a9ce2314ee7bc0d37a8264a50872e2cd5af1240798b0bbba/easy_thumbnails-2.10-py3-none-any.whl", hash = "sha256:9e446fb9da1ca0cbc99bdd1fe8fdd5c38784323d64393a37e16d13cd736980b2", size = 78939, upload-time = "2024-09-12T12:40:59.135Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
name = "graphql-core"
version = "3.2.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" },
]
[[package]]
name = "gunicorn"
version = "20.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/5b/0d1f0296485a6af03366604142ea8f19f0833894db3512a40ed07b2a56dd/gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8", size = 370601, upload-time = "2021-03-27T01:54:37.202Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/dd/5b190393e6066286773a67dfcc2f9492058e9b57c4867a95f1ba5caf0a83/gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", size = 79531, upload-time = "2021-04-27T12:16:23.375Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "mysqlclient"
version = "2.2.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/68/810093cb579daae426794bbd9d88aa830fae296e85172d18cb0f0e5dd4bc/mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845", size = 91383, upload-time = "2025-01-10T12:06:00.763Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/e0/524b0777524e0d410f71987f556dd6a0e3273fdb06cd6e91e96afade7220/mysqlclient-2.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:199dab53a224357dd0cb4d78ca0e54018f9cee9bf9ec68d72db50e0a23569076", size = 207857, upload-time = "2025-01-10T11:56:33.666Z" },
{ url = "https://files.pythonhosted.org/packages/20/40/b5d03494c1caa8f4da171db41d8d9d5b0d8959f893761597d97420083362/mysqlclient-2.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:977e35244fe6ef44124e9a1c2d1554728a7b76695598e4b92b37dc2130503069", size = 207965, upload-time = "2025-01-10T11:56:37.252Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pillow"
version = "9.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/00/d5/4903f310765e0ff2b8e91ffe55031ac6af77d982f0156061e20a4d1a8b2d/Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", size = 50488147, upload-time = "2023-04-01T09:31:37.159Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/46/672289c0ff87733fb93854dedf3a8d65642a25c0bfc88e7f6d722f9161a5/Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", size = 3395750, upload-time = "2023-04-01T09:30:10.542Z" },
{ url = "https://files.pythonhosted.org/packages/a9/70/9259e93534d01f846f7d0501f19bb7d8cc1751741bc20826fc8d3a20fe32/Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", size = 3077133, upload-time = "2023-04-01T09:30:13.386Z" },
{ url = "https://files.pythonhosted.org/packages/95/62/8a943681db5f6588498ed86aa1568dd31c63f6afdabe50841589fc662c68/Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", size = 3112534, upload-time = "2023-04-01T09:30:15.616Z" },
{ url = "https://files.pythonhosted.org/packages/f2/43/0892913d499c8df2c88dee69d59e77de19e0c51754a9be82023880641c09/Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", size = 3386725, upload-time = "2023-04-01T09:30:19.221Z" },
{ url = "https://files.pythonhosted.org/packages/ff/fc/48a51c0fe2a00d5def57b9981a1e0f8339b516351da7a51500383d833bc8/Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", size = 3308605, upload-time = "2023-04-01T09:30:29.279Z" },
{ url = "https://files.pythonhosted.org/packages/af/b7/f9faf80e3c93b02712c5748f10c75a8948e74eca61ec2408f7e1d4c9dd16/Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", size = 3164057, upload-time = "2023-04-01T09:30:32.484Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2b/57915b8af178e2c20bfd403ffed4521947881f9dbbfbaba48210dc59b9d7/Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", size = 3415613, upload-time = "2023-04-01T09:30:34.978Z" },
{ url = "https://files.pythonhosted.org/packages/e7/2a/f3ed578595f8486ee2cc07434460097d89aedd406a3db849b890ca8ec416/Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", size = 3350667, upload-time = "2023-04-01T09:30:37.849Z" },
{ url = "https://files.pythonhosted.org/packages/28/a2/f2d0d584d45100a5419fd70a1233ade8f12469ffe6e8e3acd40364beaadb/Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", size = 3414552, upload-time = "2023-04-01T09:30:48.682Z" },
{ url = "https://files.pythonhosted.org/packages/51/3a/a6701b987007aaa43559b7d8510629845b25686f09a0eb29f8946a62d767/Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", size = 2229361, upload-time = "2023-04-01T09:30:51.739Z" },
{ url = "https://files.pythonhosted.org/packages/69/72/48cc52bff8731cf72bc4101e34dc44807a410c171f921afb582a511da50e/Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", size = 2538580, upload-time = "2023-04-01T09:30:54.345Z" },
{ url = "https://files.pythonhosted.org/packages/24/35/92032a00f41bea9bf93f19d48f15daac27d1365c0038fe22dc4e7fc7c8b0/Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", size = 3349772, upload-time = "2023-04-01T09:31:13.438Z" },
{ url = "https://files.pythonhosted.org/packages/50/ce/d39869c22904558ce32e664904cf72f13a9d47703b72392e881d9e7b6082/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", size = 3281583, upload-time = "2023-04-01T09:31:16.7Z" },
{ url = "https://files.pythonhosted.org/packages/7a/75/4a382d1567efc6f4e3054f693167f8ce2d1ad939c5f6f12aa5c50f74b997/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", size = 3222603, upload-time = "2023-04-01T09:31:19.548Z" },
{ url = "https://files.pythonhosted.org/packages/51/d2/c10f72c44e000d08e41f822083cf322bb59afa7ed01ae7e3e47875b47600/Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", size = 3298174, upload-time = "2023-04-01T09:31:23.005Z" },
{ url = "https://files.pythonhosted.org/packages/02/4a/d362f7f44f1e5801c6726f0eaaeaf869d0d43c554b717072b2c5540cefb4/Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", size = 2538628, upload-time = "2023-04-01T09:31:26.082Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "tomli" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
]
[[package]]
name = "pytest-django"
version = "4.11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "ruff"
version = "0.11.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" },
{ url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" },
{ url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" },
{ url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" },
{ url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" },
{ url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" },
{ url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" },
{ url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" },
{ url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" },
{ url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" },
{ url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" },
{ url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" },
{ url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" },
{ url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" },
{ url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" },
{ url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" },
{ url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" },
]
[[package]]
name = "sentry-sdk"
version = "1.45.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c8/28/02c0cd9184f9108e3c52519f9628b215077a3854240e0b17ae845e664855/sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26", size = 244774, upload-time = "2024-07-26T13:48:32.375Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/9f/105366a122efa93f0cb1914f841747d160788e4d022d0488d2d44c2ba26c/sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1", size = 267163, upload-time = "2024-07-26T13:48:29.38Z" },
]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "sqlparse"
version = "0.5.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" },
]
[[package]]
name = "strawberry-graphql"
version = "0.270.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "graphql-core" },
{ name = "packaging" },
{ name = "python-dateutil" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/87/07/b4c411266038f9d367ce3307cf28cac74772771dfe6b4a87219311d59c90/strawberry_graphql-0.270.4.tar.gz", hash = "sha256:cca958a6070bcc2aca73a938cd80c0c7b4d39aecc8140a228608025896be990c", size = 207381, upload-time = "2025-05-29T10:40:31.544Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/de/3c7bbd77374fe814d671c607a61d6ba6cc6c25983e7cda6fa391e5c8450b/strawberry_graphql-0.270.4-py3-none-any.whl", hash = "sha256:93c6bd3bde85641ec81655c939b2daa09583e41048f94984a523539c20a8550d", size = 301421, upload-time = "2025-05-29T10:40:28.811Z" },
]
[[package]]
name = "strawberry-graphql-django"
version = "0.60.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "django" },
{ name = "strawberry-graphql" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/83/a0a902db2080469ea5f468d8077063d1c81cca99c89ae0bce6411e2e1bed/strawberry_graphql_django-0.60.0.tar.gz", hash = "sha256:a5d210bd4458be298f86d8b1b8d01ce0208291b5b6b227cd0cfd97c51cf71ee8", size = 84511, upload-time = "2025-05-24T10:01:20.863Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/a7/86c098eb3407170230fe95053e1ee0283e5bac5323d90f04e6544fdd6692/strawberry_graphql_django-0.60.0-py3-none-any.whl", hash = "sha256:6c37e9e0fe43a947732f15b43900720171e77f2f66915456a54e18bb32779772", size = 105807, upload-time = "2025-05-24T10:01:19.178Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
]
[[package]]
name = "tzdata"
version = "2025.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
]
[[package]]
name = "urllib3"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
]