Author SHA1 Message Date
alve a3178d4bff 💄 Toggle selected user 2023-08-02 23:02:57 +02:00
nikolark 79fe5cac0f 🔒 Audit 2022-04-17 21:29:02 +02:00
nikolark 95d7d13f38 ✨ Handle user not found 2022-04-17 21:13:22 +02:00
nikolark ce2cdfd409 ⚡ Code split routes 2022-04-17 16:59:00 +02:00
nikolark b56d103ae7 Merge branch 'feature/synthwave' into 'main'
✨ BBS 2222 synthwave theme

See merge request edb/bbs!3
2022-04-17 14:39:44 +00:00
nikolark 56efe6760e 💄 Fix deposit 2022-04-17 16:37:42 +02:00
nikolark ce31ef84cc 💄 Pretty deposit user select 2022-04-17 16:29:18 +02:00
nikolark 491604c720 🚚 Move ProductCard and UserCard
closer to their feature
2022-04-17 16:04:54 +02:00
nikolark 05d3dcbb3e 💄 Style product grid, rm images
♻ Move Wireframe to own component
2022-04-17 16:01:09 +02:00
nikolark 8577fb9f11 ⬆ Bump minor frontend deps
♻ Stop using React.FC
2022-04-17 15:00:10 +02:00
nikolark a038340e4a 📝 TODO 2022-03-27 21:42:27 +02:00
nikolark 34a01e9293 💄 move grid behind everything else 2022-03-27 21:41:30 +02:00
nikolark 54dd21f1eb 💄 Make you nauseous 2022-03-22 23:59:31 +01:00
nikolark 53ba5a05a1 ✨ Add credits
🐛 Fix navigating below page zero
2022-03-06 21:16:53 +01:00
nikolark f775446224 💄 Style Navlink, primary buttons and headings 2022-03-06 15:16:16 +01:00
nikolark 13cc0d92a2 💄 Put new brand in header and fix toasts 2022-03-06 14:27:52 +01:00
nikolark 7bfa587a77 💄 Avatar rhombus 2022-03-06 13:34:05 +01:00
nikolark 3d91148b7e 💄 First round of styling
✨ Added new splash
2022-03-06 13:13:08 +01:00
nikolark 54bbd75730 🔥 Remove all things bootstrap 2022-03-05 20:00:03 +01:00
nikolark 06f43b06b8 ⚡ Avoid redirect 2022-02-20 12:36:15 +01:00
nikolark ad116c2c28 💚 Update pytest config with new app name 2022-02-20 01:33:16 +01:00
nikolark eb960bdb84 🔧 Update scripts with new path 2022-02-20 01:32:36 +01:00
nikolark f4c99153ca 🔧 Point VITE_API_URL to the right place 2022-02-20 01:31:03 +01:00
nikolark 3e457de416 🐛 Fix VITE_API_URL reference 2022-02-20 01:25:55 +01:00
nikolark faca98b7f9 🔧 Reference static assests from / 2022-02-20 01:20:41 +01:00
nikolark 53ef262ba4 🔧 Build frontend on deploy 2022-02-20 01:16:13 +01:00
nikolark f4749e6a6e 🔧 output frontend build to root static folder 2022-02-20 01:12:37 +01:00
nikolark e5de16f227 🐛 Remove 3.9 type syntax 2022-02-20 00:57:12 +01:00
nikolark eace79f012 ⬇ Downgrade to python 3.8 2022-02-20 00:54:00 +01:00
nikolark 9b903f42d6 🚚 Rename app main to bbs
🚚 Move settings into main
➕ Add django-rename-app
2022-02-20 00:39:16 +01:00
nikolark e1eaf3df98 💚 Try fixing deploy script 2022-02-20 00:17:51 +01:00
nikolark b79d9fc9c8 👷 Add ssh config 2022-02-20 00:08:15 +01:00
nikolark 42f23b1d77 Merge branch 'chore/bbs-bumps' into 'master'
💥 Rewrite in react

See merge request edb/bbs!1
2022-02-19 22:39:07 +00:00
98 changed files with 2274 additions and 1505 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
VITE_API_URL=http://localhost:3000
+4 -1
View File
@@ -11,4 +11,7 @@ CACHE/
media/
tmp/
frontend/dist
static/modern
static/assets
static/index.html
.python-version
.env
+12 -1
View File
@@ -2,7 +2,7 @@ variables:
PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip"
default:
image: nikolaik/python-nodejs:python3.9-nodejs16
image: nikolaik/python-nodejs:python3.8-nodejs16
cache:
paths:
- .cache/pip
@@ -29,6 +29,17 @@ lint:
deploy:
stage: deploy
before_script:
# Install ssh if not already
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
# Make SSH key available
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Add known host keys
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- ssh gitdeploy@dreamcast.neuf.no "$(< bin/deploy)"
only:
+2 -2
View File
@@ -9,6 +9,7 @@ poetry shell
pre-commit install
python manage.py migrate
bin/run
cp .env-default .env
# in another terminal
cd frontend
@@ -22,9 +23,8 @@ npm start
## TODO
- update emails
- improve product listing (light purple background)
- Stats: products over time
- Light on dark colors
- inventory views
View File
File renamed without changes.
+1 -1
View File
@@ -1,6 +1,6 @@
from django.contrib import admin
from main.models import *
from bbs.models import *
class ProductAdmin(admin.ModelAdmin):
View File
File renamed without changes.
+3 -1
View File
@@ -1,3 +1,5 @@
from typing import List
import strawberry
@@ -17,6 +19,6 @@ class FieldError(Error):
@strawberry.type
class FormErrors:
fields: list[FieldError]
fields: List[FieldError]
message: str = ""
error: bool = True
File renamed without changes.
@@ -1,3 +1,5 @@
from typing import List
import strawberry
from django.conf import settings
from django.contrib.auth import get_user_model
@@ -5,10 +7,10 @@ from django.db import transaction
from django.shortcuts import get_object_or_404
from strawberry import ID
from main import models
from main.api.errors import Error, FieldError, FormErrors
from main.api.types.inventory import Transaction
from main.api.types.shop import Order
from bbs import models
from bbs.api.errors import Error, FieldError, FormErrors
from bbs.api.types.inventory import Transaction
from bbs.api.types.shop import Order
UserModel = get_user_model()
@@ -34,7 +36,7 @@ CreateOrderResponse = strawberry.union("CreateOrderResponse", [CreateOrderSucces
@strawberry.mutation
@transaction.atomic
def create_order(customer_id: ID, order_lines: list[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
def create_order(customer_id: ID, order_lines: List[OrderLineInput], is_external: bool = False) -> CreateOrderResponse:
# clean and validate
try:
user = UserModel.objects.get(pk=customer_id)
@@ -4,8 +4,8 @@ from django.contrib.auth import get_user_model
from django.db import transaction
from strawberry_django import auto
from main import models
from main.api.types.users import User
from bbs import models
from bbs.api.types.users import User
DjangoUser = get_user_model()
File renamed without changes.
+11
View File
@@ -0,0 +1,11 @@
from typing import List
import strawberry
import strawberry_django
from bbs.api.types.inventory import Transaction, TransactionOrdering
@strawberry.type
class InventoryQueries:
transaction_list: List[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
@@ -1,15 +1,17 @@
from typing import List
import strawberry
import strawberry_django
from main import models
from main.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
from bbs import models
from bbs.api.types.shop import Order, OrderOrdering, OrderStats, OrderStatsByTime, Point, Product, ProductOrderStat
@strawberry.type
class ShopQueries:
all_products: list[Product] = strawberry_django.field()
all_orders: list[Order] = strawberry_django.field()
order_list: list[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
all_products: List[Product] = strawberry_django.field()
all_orders: List[Order] = strawberry_django.field()
order_list: List[Order] = strawberry_django.field(pagination=True, order=OrderOrdering)
@strawberry.field
def order_stats(self) -> OrderStats:
@@ -24,7 +26,7 @@ class ShopQueries:
)
@strawberry.field
def product_stats(self) -> list[ProductOrderStat]:
def product_stats(self) -> List[ProductOrderStat]:
return [
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
for stat in models.OrderLine.objects.product_order_stats().as_series()
@@ -1,14 +1,16 @@
from typing import List
import strawberry
import strawberry_django
from django.contrib.auth.models import User as DjangoUser
from strawberry import ID
from main.api.types.users import User
from bbs.api.types.users import User
@strawberry.type
class UsersQueries:
all_users: list[User] = strawberry_django.field()
all_users: List[User] = strawberry_django.field()
@strawberry.field
def user(self, user_id: ID) -> User:
+34
View File
@@ -0,0 +1,34 @@
import strawberry
from django.core.exceptions import ObjectDoesNotExist
from strawberry.extensions import Extension
from bbs.api.mutations.shop import ShopMutations
from bbs.api.mutations.users import UsersMutations
from bbs.api.queries.inventory import InventoryQueries
from bbs.api.queries.shop import ShopQueries
from bbs.api.queries.users import UsersQueries
@strawberry.type
class Query(InventoryQueries, ShopQueries, UsersQueries):
pass
@strawberry.type
class Mutation(ShopMutations, UsersMutations):
pass
class HandleNotFound(Extension):
def on_request_end(self):
result = self.execution_context.result
if result.errors:
for error in result.errors:
if isinstance(error.original_error, ObjectDoesNotExist):
error.extensions = {
"code": "NOT_FOUND",
"model": type(error.original_error).__qualname__.replace(".DoesNotExist", ""),
}
schema = strawberry.Schema(query=Query, mutation=Mutation, extensions=[HandleNotFound])
File renamed without changes.
@@ -1,8 +1,8 @@
import strawberry_django
from strawberry_django import auto
from main import models
from main.api.types.users import User
from bbs import models
from bbs.api.types.users import User
@strawberry_django.ordering.order(models.Transaction)
@@ -1,9 +1,11 @@
from typing import List
import strawberry
import strawberry_django
from strawberry_django import auto
from main import models
from main.api.types.utils import JSONScalar
from bbs import models
from bbs.api.types.utils import JSONScalar
@strawberry_django.filters.filter(models.Product)
@@ -31,10 +33,10 @@ class OrderOrdering:
@strawberry_django.type(models.Order)
class Order:
id: auto
customer: strawberry.LazyType["User", "main.api.types.users"]
customer: strawberry.LazyType["User", "bbs.api.types.users"]
order_sum: auto
created: auto
orderlines: list["OrderLine"]
orderlines: List["OrderLine"]
is_external: bool
@@ -55,10 +57,10 @@ class OrderStatsByTime:
@strawberry.type
class OrderStats:
yearly: list[OrderStatsByTime]
monthly: list[OrderStatsByTime]
daily: list[OrderStatsByTime]
hourly: list[OrderStatsByTime]
yearly: List[OrderStatsByTime]
monthly: List[OrderStatsByTime]
daily: List[OrderStatsByTime]
hourly: List[OrderStatsByTime]
@strawberry.type
@@ -70,4 +72,4 @@ class Point:
@strawberry.type
class ProductOrderStat:
product_name: str
data: list[Point]
data: List[Point]
@@ -1,12 +1,13 @@
from datetime import datetime
from typing import List
import strawberry
import strawberry_django
from django.contrib.auth import get_user_model
from strawberry_django import auto
from main import models
from main.api.types.shop import Point, ProductOrderStat
from bbs import models
from bbs.api.types.shop import Point, ProductOrderStat
UserModel = get_user_model()
@@ -27,11 +28,11 @@ class UserProfile:
order_sum_total: float
@strawberry.field()
def top_months(self) -> list[TopMonth]:
def top_months(self) -> List[TopMonth]:
return [TopMonth(**month) for month in self.top_months()]
@strawberry.field()
def product_order_stats(self) -> list[ProductOrderStat]:
def product_order_stats(self) -> List[ProductOrderStat]:
return [
ProductOrderStat(product_name=stat["product_name"], data=[Point(**p) for p in stat["data"]])
for stat in self.product_order_stats().as_series()
File renamed without changes.
+1 -1
View File
@@ -2,7 +2,7 @@ from django.apps import AppConfig
class BBSAppConfig(AppConfig):
name = "main"
name = "bbs"
verbose_name = "Biceps Bar System"
def ready(self):
+1 -1
View File
@@ -1,6 +1,6 @@
from django import forms
from main.models import InventoryTransaction, Product
from bbs.models import InventoryTransaction, Product
class InventoryTransactionForm(forms.ModelForm):
File renamed without changes.
File renamed without changes.
@@ -1,10 +1,10 @@
from pathlib import Path
from django.conf import settings
from django.core.management import BaseCommand, CommandParser
from strawberry.printer import print_schema
from bbssite.settings import BASE_DIR
from main.api.schema import schema as schema_symbol
from bbs.api.schema import schema as schema_symbol
class Command(BaseCommand):
@@ -16,7 +16,7 @@ class Command(BaseCommand):
def handle(self, *args, filename=None, **options):
schema_output = print_schema(schema_symbol)
if filename != "-":
with Path(BASE_DIR, filename).open("w+") as fp:
with Path(settings.BASE_DIR, filename).open("w+") as fp:
fp.write(schema_output)
self.stdout.write(self.style.SUCCESS("OK"))
else:
@@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand, CommandParser
from main.models import UserProfile
from bbs.models import UserProfile
class Command(BaseCommand):
@@ -72,7 +72,7 @@ class Migration(migrations.Migration):
models.ForeignKey(
on_delete=models.CASCADE,
related_name="orderlines",
to="main.Order",
to="bbs.Order",
),
),
],
@@ -157,7 +157,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="orderline",
name="product",
field=models.ForeignKey(on_delete=models.CASCADE, related_name="orderlines", to="main.Product"),
field=models.ForeignKey(on_delete=models.CASCADE, related_name="orderlines", to="bbs.Product"),
),
migrations.AddField(
model_name="inventorytransaction",
@@ -166,7 +166,7 @@ class Migration(migrations.Migration):
on_delete=models.CASCADE,
related_name="transactions",
verbose_name="Produkt",
to="main.Product",
to="bbs.Product",
),
),
migrations.AddField(
File renamed without changes.
+3 -3
View File
@@ -146,8 +146,8 @@ class OrderLineManager(models.QuerySet):
class OrderLine(models.Model):
order = models.ForeignKey("main.Order", on_delete=models.CASCADE, related_name="orderlines")
product = models.ForeignKey("main.Product", on_delete=models.CASCADE, related_name="orderlines")
order = models.ForeignKey("bbs.Order", on_delete=models.CASCADE, related_name="orderlines")
product = models.ForeignKey("bbs.Product", on_delete=models.CASCADE, related_name="orderlines")
amount = models.IntegerField()
unit_price = models.FloatField(verbose_name="Enhetspris")
@@ -184,7 +184,7 @@ class InventoryTransaction(models.Model):
related_name="inventory_transactions",
)
product = models.ForeignKey(
"main.Product",
"bbs.Product",
on_delete=models.CASCADE,
related_name="transactions",
verbose_name="Produkt",
+3 -2
View File
@@ -72,7 +72,7 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
]
ROOT_URLCONF = "bbssite.urls"
ROOT_URLCONF = "bbs.urls"
INSTALLED_APPS = [
"django.contrib.auth",
@@ -85,9 +85,10 @@ INSTALLED_APPS = [
"django_extensions",
"easy_thumbnails",
"corsheaders",
"django_rename_app",
]
LOCAL_APPS = [
"main",
"bbs",
]
INSTALLED_APPS += LOCAL_APPS
@@ -2,7 +2,7 @@ import pytest
from django.test import Client
from strawberry.django.test import GraphQLTestClient
from main.models import Order, Product
from bbs.models import Order, Product
@pytest.fixture(name="gql_client")
Executable → Regular
+7 -4
View File
@@ -2,16 +2,19 @@ from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import include, path, re_path
from django.urls import path, re_path
from strawberry.django.views import GraphQLView
from main import urls as main_urls
from main.api.schema import schema
from bbs.api.schema import schema
from bbs.views.reports import inventory, inventory_add, report
admin.autodiscover()
urlpatterns = [
re_path(r"", include(main_urls)),
# TODO: migrate reports to SPA
re_path(r"^inventory/$", inventory, name="inventory"),
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
re_path(r"^report$", report, name="report"),
path("accounts/login/", LoginView.as_view(), name="django.contrib.auth.views.login"),
path("accounts/logout/", LogoutView.as_view(), name="django.contrib.auth.views.logout"),
path("admin/", admin.site.urls),
View File
File renamed without changes.
File renamed without changes.
@@ -4,8 +4,8 @@ from itertools import groupby
from django.contrib import messages
from django.shortcuts import redirect, render
from main.forms import DateRangeForm, InventoryTransactionForm
from main.models import InventoryTransaction, Order, Product, Transaction, UserProfile
from bbs.forms import DateRangeForm, InventoryTransactionForm
from bbs.models import InventoryTransaction, Order, Product, Transaction, UserProfile
def report(request):
+1 -1
View File
@@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbssite.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbs.settings")
application = get_wsgi_application()
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -eu
cd frontend
npm i
npm run build
+10 -2
View File
@@ -1,14 +1,22 @@
#!/bin/bash
set -eu
export VITE_API_URL="https://bbs.neuf.no/api"
PROJECT_PATH=/var/www/neuf.no/bbs
PROCESS_NAME="$PROJECT.neuf.no"
PROCESS_NAME="bbs.neuf.no"
echo "Deploying bbs to ${PROJECT_PATH}"
cd "$PROJECT_PATH" || exit 1
git pull
# Backend
poetry install
umask 022; poetry run python manage.py collectstatic --noinput # Collect static
poetry run python manage.py migrate # Run DB migrations
# Reload
# Frontend
bin/build-frontend
# Reload backend
echo "Reloading supervisor process ${PROCESS_NAME}"
sudo /usr/bin/supervisorctl pid "$PROCESS_NAME" | xargs kill -HUP
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
SRC_DIR="main bbssite"
SRC_DIR="bbs"
black $SRC_DIR
isort $SRC_DIR
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
SRC_DIR="main bbssite"
SRC_DIR="bbs"
black --check $SRC_DIR
isort --diff $SRC_DIR
+3
View File
@@ -1 +1,4 @@
#!/bin/bash
cd frontend && npm run start
+3
View File
@@ -3,6 +3,9 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BBS</title>
</head>
+949 -984
View File
File diff suppressed because it is too large. Load diff
+21 -24
View File
@@ -16,51 +16,48 @@
"test": "jest"
},
"dependencies": {
"@apollo/client": "^3.5.8",
"@apollo/client": "^3.5.10",
"@nivo/bar": "^0.79.1",
"@nivo/line": "^0.79.1",
"@nivo/pie": "^0.79.1",
"bootstrap": "^5.1.3",
"bootstrap-icons": "^1.8.1",
"clsx": "^1.1.1",
"date-fns": "^2.28.0",
"graphql": "^16.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.27.1",
"react-router-dom": "^6.2.1",
"react-hook-form": "^7.30.0",
"react-router-dom": "^6.3.0",
"use-url-search-params": "^2.5.1"
},
"devDependencies": {
"@graphql-codegen/cli": "^2.6.1",
"@graphql-codegen/typed-document-node": "^2.2.3",
"@graphql-codegen/typescript-operations": "^2.3.0",
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/typed-document-node": "^2.2.8",
"@graphql-codegen/typescript-operations": "^2.3.5",
"@graphql-typed-document-node/core": "^3.1.1",
"@types/bootstrap": "^5.1.9",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"@types/react": "^17.0.44",
"@types/react-dom": "^17.0.15",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
"@vitejs/plugin-react": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"@vitejs/plugin-react": "^1.3.1",
"@vitejs/plugin-react-refresh": "^1.3.6",
"eslint": "^8.9.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"jest": "^27.5.1",
"prettier": "^2.5.1",
"stylelint": "^14.5.0",
"prettier": "^2.6.2",
"stylelint": "^14.7.0",
"stylelint-config-idiomatic-order": "^8.1.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^7.0.0",
"stylelint-order": "^5.0.0",
"stylelint-prettier": "^2.0.0",
"typescript": "^4.5.5",
"vite": "^2.8.1",
"vite-plugin-svgr": "^1.0.1"
"typescript": "^4.6.3",
"vite": "^2.9.5",
"vite-plugin-svgr": "^1.1.0"
},
"engines": {
"node": ">=16",
+2 -1
View File
@@ -1,3 +1,4 @@
.app {
margin: 16px;
margin: 1rem auto;
max-width: 1220px;
}
+5 -2
View File
@@ -1,23 +1,26 @@
import { ApolloProvider } from "@apollo/client";
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import styles from "./App.module.css";
import { Routes } from "./Routes";
import client from "./client";
import { Footer } from "./components/Footer";
import { Header } from "./components/Header";
import { Toast } from "./components/Toast";
import { ToastProvider } from "./components/ToastProvider";
import { Wireframe } from "./components/Wireframe";
const App: React.FC = () => (
const App = (): JSX.Element => (
<ApolloProvider client={client}>
<Router>
<ToastProvider>
<Header />
<div className={styles.app}>
<Routes />
<Wireframe />
</div>
<Footer />
<Toast />
</ToastProvider>
</Router>
+14 -10
View File
@@ -1,17 +1,19 @@
import React from "react";
import { Route, Routes as ReactRouterRoutes } from "react-router-dom";
import { UserCreate } from "./features/auth/UserCreate";
import { Deposit } from "./features/deposit/Deposit";
import { Log } from "./features/log/Log";
import { Register } from "./features/register/Register";
import { OrderStats } from "./features/stats/OrderStats";
import { ProductStats } from "./features/stats/ProductStats";
import { Stats } from "./features/stats/Stats";
import { UserDetail } from "./features/users/UserDetail";
import { UserList } from "./features/users/UserList";
const Splash = React.lazy(() => import("./components/Splash"));
const UserCreate = React.lazy(() => import("./features/auth/UserCreate"));
const Deposit = React.lazy(() => import("./features/deposit/Deposit"));
const Log = React.lazy(() => import("./features/log/Log"));
const Register = React.lazy(() => import("./features/register/Register"));
const OrderStats = React.lazy(() => import("./features/stats/OrderStats"));
const ProductStats = React.lazy(() => import("./features/stats/ProductStats"));
const Stats = React.lazy(() => import("./features/stats/Stats"));
const UserDetail = React.lazy(() => import("./features/users/UserDetail"));
const UserList = React.lazy(() => import("./features/users/UserList"));
export const Routes: React.FC = () => (
export const Routes = (): JSX.Element => (
<React.Suspense fallback={<div>Loading...</div>}>
<ReactRouterRoutes>
<Route path="/deposit" element={<Deposit />} />
<Route path="/log" element={<Log />} />
@@ -22,5 +24,7 @@ export const Routes: React.FC = () => (
<Route path="/users/create" element={<UserCreate />} />
<Route path="/user/:userId" element={<UserDetail />} />
<Route path="/" element={<Register />} />
<Route path="*" element={<Splash brandText="404" noOverlay />} />
</ReactRouterRoutes>
</React.Suspense>
);
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,7 +1,7 @@
import { ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
uri: import.meta.env.VITE_API_URL || "http://localhost:8000/graphql/",
uri: `${import.meta.env.VITE_API_URL}/graphql/`,
cache: new InMemoryCache(),
});
+22
View File
@@ -0,0 +1,22 @@
.footer {
position: absolute;
right: 0;
bottom: 1rem;
left: 0;
height: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: 0.8rem;
text-align: center;
}
.heart {
color: var(--brand);
}
.brick {
position: relative;
top: 7px;
display: inline-block;
width: 20px;
height: 20px;
}
+15
View File
@@ -0,0 +1,15 @@
import React from "react";
import { ReactComponent as Brick } from "../assets/brick.svg";
import styles from "./Footer.module.css";
export const Footer = (): JSX.Element => {
return (
<footer className={styles.footer}>
Made with <span className={styles.heart}>❤</span> by{" "}
<span title="EDB-gjengen">
<Brick className={styles.brick} />
</span>
</footer>
);
};
@@ -0,0 +1,9 @@
.errorMessage {
font-family: Rocket Rinder, monospace;
font-size: 3rem;
font-style: italic;
color: var(--brand);
text-align: center;
width: 100%;
margin-top: 30vh;
}
+23
View File
@@ -0,0 +1,23 @@
import { ApolloError } from "@apollo/client";
import React from "react";
import styles from "./GQLError.module.css";
const formatModel = (text: string) => {
const withSpaces = text
.replace(/([A-Z])/g, " $1")
.toLocaleLowerCase()
.trim();
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
};
export const GQLError = ({ error }: { error: ApolloError }) => {
if (error?.graphQLErrors?.length == 1) {
const single = error?.graphQLErrors[0];
if (single?.extensions?.code === "NOT_FOUND") {
const modelPretty = formatModel(single?.extensions?.model as string);
return <div className={styles.errorMessage}>{`${modelPretty} not found`}</div>;
}
}
return <div className={styles.errorMessage}>Error...</div>;
};
+90 -9
View File
@@ -1,13 +1,94 @@
.logo {
position: relative;
top: -1px;
display: inline-block;
width: 24px;
height: 24px;
margin: 0 8px;
.header {
margin: 1rem auto;
max-width: 1220px;
}
.navIcon {
.nav {
display: flex;
height: 5rem;
align-items: center;
text-transform: uppercase;
}
.navLink:after {
position: absolute;
right: 12px;
left: 14px;
display: block;
height: 1px;
background-color: rgba(243, 33, 61, 0.4);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.4), 0 0 11px rgba(243, 33, 61, 0.4), 0 0 19px rgba(243, 33, 61, 0.4);
content: "";
transform: rotate(-2.5deg);
}
.navLink:hover:after {
background-color: var(--brand);
box-shadow: 0 0 4px var(--brand), 0 0 11px var(--brand), 0 0 19px var(--brand), 0 0 30px var(--brand);
}
.logoNav {
margin-left: 8px;
}
.navItems {
display: flex;
align-items: center;
padding: 0;
margin: 0;
list-style: none;
}
.navLink {
position: relative;
top: -2px;
padding: 0.4rem 0.8rem;
border: 1px solid transparent;
transition: color 0.1s ease-in-out;
}
.navLink:hover {
color: white;
}
.active.active {
color: white;
}
/* FIXME: Duplicates Splash styles */
.brand {
padding-left: 1rem;
color: var(--brand);
font-family: Rocket Rinder, monospace;
font-size: 3rem;
font-style: italic;
letter-spacing: 2px;
line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
}
.subtitle {
position: relative;
left: -8px;
animation: flicker 1.5s infinite alternate;
color: var(--interlace);
font-family: Rocket Clouds DEMO, serif;
font-size: 10px;
font-weight: bold;
line-height: 1;
transform: rotate(-6deg);
}
@keyframes flicker {
0%,
18%,
22%,
25%,
53%,
57%,
100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8);
}
20%,
24%,
55% {
text-shadow: none;
}
}
+17 -52
View File
@@ -1,85 +1,50 @@
import { ReactComponent as BarChart } from "bootstrap-icons/icons/bar-chart.svg";
import { ReactComponent as CardList } from "bootstrap-icons/icons/card-list.svg";
import { ReactComponent as House } from "bootstrap-icons/icons/house.svg";
import { ReactComponent as PeopleIcon } from "bootstrap-icons/icons/people.svg";
import clsx from "clsx";
import React from "react";
import { NavLink } from "react-router-dom";
import { ReactComponent as Logo } from "../assets/brick.svg";
import styles from "./Header.module.css";
const ADMIN_URL = import.meta.env.VITE_API_URL
? `${import.meta.env.VITE_API_URL}/admin`
: "http://localhost:8000/admin/";
const ADMIN_URL = `${import.meta.env.VITE_API_URL}/admin/`;
const navLinkStyles = ({ isActive }: { isActive: boolean }) => (isActive ? "nav-link active" : "nav-link");
const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive });
export const Header: React.FC = () => {
export const Header = (): JSX.Element => {
return (
<header>
<nav className="navbar navbar-dark bg-dark navbar-expand-md">
<NavLink to="/" className="navbar-brand">
<span className={styles.logo}>
<Logo />
</span>
Biceps Bar System - BBS
<header className={styles.header}>
<nav className={styles.nav}>
<NavLink to="/" className={styles.logoNav}>
<div className={styles.brand}>BBS</div>
<div className={styles.subtitle}>Biceps Bar System</div>
</NavLink>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon" />
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className="nav-item">
<ul className={styles.navItems}>
<li>
<NavLink to="/" className={navLinkStyles} end>
<span className={styles.navIcon}>
<House />
</span>{" "}
Kjøp drikke
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink to="/deposit" className={navLinkStyles}>
Sett inn penger
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink to="/log" className={navLinkStyles}>
<span className={styles.navIcon}>
<CardList />
</span>{" "}
Logg
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink to="/stats" className={navLinkStyles}>
<span className={styles.navIcon}>
<BarChart />
</span>{" "}
Statistikk
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink to="/users" className={navLinkStyles}>
<span className={styles.navIcon}>
<PeopleIcon />
</span>{" "}
Folk
</NavLink>
</li>
</ul>
</div>
<ul className="navbar-nav">
<li className="nav-item">
<a href={ADMIN_URL} className="nav-link">
<li>
<a href={ADMIN_URL} className={styles.navLink}>
Admin
</a>
</li>
@@ -1,57 +0,0 @@
.product {
display: flex;
overflow: hidden;
flex-direction: column;
align-items: center;
justify-content: center;
border: 4px solid transparent;
background: transparent;
border-radius: 8px;
transition: background-color 0.1s ease-out, border 0.1s ease-out;
}
.product:hover {
border-color: rgba(25, 135, 84, 0.6);
background: rgba(0, 0, 0, 0.1);
}
.active {
border-color: rgba(25, 135, 84, 0.6);
background: rgba(25, 135, 84, 0.2);
}
.active:hover {
background: rgba(25, 135, 84, 0.2);
}
.productText {
position: absolute;
right: 0;
bottom: 0;
left: 0;
padding: 4px;
background: rgba(0, 0, 0, 0.6);
border-radius: 8px;
color: white;
font-weight: bold;
}
.productImage {
width: 140px;
height: 140px;
object-fit: contain;
}
.noProductImage {
width: 140px;
height: 140px;
}
.amount {
position: absolute;
top: 0px;
left: 0px;
padding: 4px;
background: rgba(0, 0, 0, 0.6);
border-radius: 8px;
color: white;
font-size: 2rem;
font-weight: bold;
line-height: 1;
}
.wrap {
position: relative;
}
-26
View File
@@ -1,26 +0,0 @@
import clsx from "clsx";
import React, { MouseEventHandler } from "react";
import { Product } from "../types";
import styles from "./ProductCard.module.css";
type ProductCardProps = {
product: Omit<Product, "userCounts">;
onProduct: MouseEventHandler;
active: boolean;
amount: number;
};
export const ProductCard: React.FC<ProductCardProps> = ({ product, onProduct, amount, active = false }) => {
const classes = clsx(styles.product, { [styles.active]: active });
return (
<button className={classes} type="button" onClick={onProduct}>
<div className={styles.wrap}>
<span className={styles.amount}>{amount}</span>
{product.imageUrl && <img src={product.imageUrl} className={styles.productImage} alt={product.name} />}
{!product.imageUrl && <div className={styles.noProductImage} />}
<span className={styles.productText}>{product.name}</span>
</div>
</button>
);
};
+68
View File
@@ -0,0 +1,68 @@
.splash {
top: 0;
right: 0;
bottom: 0;
left: 0;
display: grid;
place-items: center;
text-align: center;
}
.overlay {
position: fixed;
}
.wrap {
position: relative;
}
.sun {
width: clamp(100px, 65vw, 600px);
height: clamp(100px, 65vw, 600px);
margin: 0 auto;
background: linear-gradient(var(--sun) 50%, var(--sun-end) 90%);
border-radius: 50% 50%;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun), 0 0 30px var(--sun);
}
.brand {
position: relative;
left: -0.6rem;
padding-top: 3rem;
color: var(--brand);
font-family: Rocket Rinder, monospace;
font-size: clamp(20px, 20vw, 200px);
font-style: italic;
letter-spacing: 1rem;
line-height: 1;
text-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
}
.subtitle {
position: relative;
bottom: 0.5rem;
left: 0rem;
animation: flicker 1.5s infinite alternate;
color: var(--interlace);
font-family: Rocket Clouds DEMO, serif;
font-size: clamp(20px, 5vw, 50px);
font-weight: bold;
line-height: 1;
transform: rotate(-6deg);
}
@keyframes flicker {
0%,
18%,
22%,
25%,
53%,
57%,
100% {
text-shadow: 0 0 4px rgba(121, 152, 238, 0.8), 0 0 11px rgba(121, 152, 238, 0.8), 0 0 19px rgba(121, 152, 238, 0.8);
}
20%,
24%,
55% {
text-shadow: none;
}
}
+20
View File
@@ -0,0 +1,20 @@
import clsx from "clsx";
import React from "react";
import styles from "./Splash.module.css";
interface SplashProps {
brandText?: string;
noOverlay?: boolean;
}
const Splash = ({ brandText = "BBS", noOverlay = false }: SplashProps): JSX.Element => (
<div className={clsx(styles.splash, { [styles.overlay]: !noOverlay })}>
<div className={styles.wrap}>
<div className={styles.sun}></div>
<div className={styles.brand}>{brandText}</div>
<div className={styles.subtitle}>Biceps Bar System</div>
</div>
</div>
);
export default Splash;
+23
View File
@@ -2,4 +2,27 @@
position: absolute;
bottom: 32px;
left: 32px;
padding: 0.6rem 1.2rem;
border-radius: 4px;
opacity: 0;
pointer-events: none;
transition: opacity 0.1s ease-in-out;
}
.visible {
opacity: 1;
pointer-events: initial;
}
.primary {
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
}
.danger {
background: var(--brand);
box-shadow: 0 0 4px rgba(243, 33, 61, 0.8), 0 0 11px rgba(243, 33, 61, 0.8), 0 0 19px rgba(243, 33, 61, 0.8);
}
.close {
display: none;
}
+7 -30
View File
@@ -1,41 +1,18 @@
import { Toast as BSToast } from "bootstrap";
import clsx from "clsx";
import React, { useEffect, useRef } from "react";
import React from "react";
import styles from "./Toast.module.css";
import { useToast } from "./ToastProvider";
export const Toast: React.FC = () => {
const ref = useRef<HTMLInputElement>(null);
const { visible, showToast, message, background } = useToast();
export const Toast = (): JSX.Element => {
const { showToast, message, background, visible } = useToast();
const backgroundColor = `bg-${background}`;
const classes = clsx("toast align-items-center text-white bg-primary border-0", backgroundColor, styles.toast);
useEffect(() => {
if (!ref.current) return;
const bsToast = BSToast.getInstance(ref.current) || new BSToast(ref.current);
if (visible) {
bsToast?.show();
} else if (!visible) {
bsToast?.hide();
}
return () => bsToast?.hide();
}, [visible]);
const classes = clsx([styles[background], styles.toast], { [styles.visible]: visible });
return (
<div ref={ref} className={classes} role="alert" aria-live="assertive" aria-atomic="true">
<div className="d-flex">
<div className="toast-body">{message}</div>
<button
type="button"
className="btn-close btn-close-white me-2 m-auto"
data-bs-dismiss="toast"
aria-label="Close"
onClick={() => showToast(message)}
/>
</div>
<div className={classes} role="alert" aria-live="assertive" aria-atomic="true">
<div>{message}</div>
<button type="button" aria-label="Close" onClick={() => showToast(message)} className={styles.close} />
</div>
);
};
+10 -8
View File
@@ -1,21 +1,24 @@
import React, { ReactNode, useContext, useState } from "react";
type Background = "primary" | "danger";
interface ToastProps {
visible: boolean;
message: string;
showToast: (message: string, background?: string) => void;
showToast: (message: string, background?: Background) => void;
hide: () => void;
background: string;
background: Background;
}
const defaultState = {
// FIXME: revert
visible: false,
message: "",
// eslint-disable-next-line @typescript-eslint/no-empty-function
showToast: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
hide: () => {},
background: "primary",
background: "primary" as Background,
};
const ToastContext = React.createContext<ToastProps>(defaultState);
@@ -23,19 +26,18 @@ const ToastContext = React.createContext<ToastProps>(defaultState);
type ToastProviderProps = {
children: ReactNode;
};
export const ToastProvider: React.FC<ToastProviderProps> = ({ children }) => {
export const ToastProvider = ({ children }: ToastProviderProps): JSX.Element => {
const [visible, setVisible] = useState(defaultState.visible);
const [message, setMessage] = useState("");
const [background, setBackground] = useState("");
const [message, setMessage] = useState(defaultState.message);
const [background, setBackground] = useState(defaultState.background);
const showToast = (newMessage: string, background = "primary") => {
const showToast = (newMessage: string, background: Background = "primary") => {
setMessage(newMessage);
setBackground(background);
if (!visible) {
setVisible(true);
setTimeout(() => {
setVisible(false);
setMessage("");
}, 3000);
}
};
@@ -1,34 +0,0 @@
.user {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px;
border: 3px solid transparent;
background: transparent;
border-radius: 8px;
transition: background-color 0.1s ease-out, border 0.1s ease-out;
}
.user:hover {
border-color: rgba(25, 135, 84, 0.6);
background: rgba(0, 0, 0, 0.1);
}
.active {
border-color: rgba(25, 135, 84, 0.6);
background: rgba(25, 135, 84, 0.2);
}
.active:hover {
background: rgba(25, 135, 84, 0.2);
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50px;
}
.avatar svg {
width: 50px;
height: 50px;
color: #ccc;
}
.name {
font-weight: bold;
}
-31
View File
@@ -1,31 +0,0 @@
import { ReactComponent as PersonFill } from "bootstrap-icons/icons/person-fill.svg";
import clsx from "clsx";
import React, { MouseEventHandler } from "react";
import { AllUsersQuery } from "../types";
import styles from "./UserCard.module.css";
type UserCardProps = {
user: AllUsersQuery["allUsers"][0] | null;
onUser: MouseEventHandler;
active: boolean;
};
/** If user is null, then it's the special external user */
export const UserCard: React.FC<UserCardProps> = ({ user, onUser, active = false }) => {
const classes = clsx(styles.user, { [styles.active]: active });
return (
<button className={classes} onClick={onUser} type="button">
{user?.profile?.image && (
<img src={user.profile?.image} alt={`Profile for ${user.firstName}`} className={styles.avatar} />
)}
{!user?.profile?.image && (
<span className={styles.avatar}>
<PersonFill />
</span>
)}
<span className={styles.name}>{user?.firstName || "Ekstern"}</span>
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
</button>
);
};
@@ -0,0 +1,38 @@
.wireframe {
position: absolute;
bottom: calc(22% - 512px);
left: calc(50% - 512px);
width: 1024px;
height: 1024px;
display: grid;
grid-template-columns: repeat(8, 128px);
animation-iteration-count: infinite;
animation-name: isometric-rotate;
animation-duration: 10s;
animation-timing-function: linear;
pointer-events: none;
z-index: -1;
}
.cell {
border-bottom: 2px solid var(--sun);
border-right: 2px solid var(--sun);
}
.cell:nth-child(8n) {
border-right: none;
}
.cell:nth-child(n+57) {
border-bottom: none;
}
@keyframes isometric-rotate {
0% {
transform: rotateX(70deg) rotateY(0deg) rotateZ(-45deg);
}
100% {
transform: rotateX(70deg) rotateY(0deg) rotateZ(-405deg);
}
}
+11
View File
@@ -0,0 +1,11 @@
import React from "react";
import styles from "./Wireframe.module.css";
export const Wireframe = () => (
<div className={styles.wireframe}>
{[...Array(64)].map((_, idx) => (
<div key={idx} className={styles.cell} />
))}
</div>
);
@@ -0,0 +1,5 @@
.form {
display: grid;
max-width: 500px;
gap: 1rem;
}
+8 -6
View File
@@ -3,6 +3,7 @@ import React from "react";
import { SubmitHandler, useForm } from "react-hook-form";
import { CreateUserDocument } from "../../types";
import styles from "./UserCreate.module.css";
type FormValues = {
firstName: string;
@@ -10,7 +11,7 @@ type FormValues = {
email: string;
};
export const UserCreate = () => {
const UserCreate = () => {
const {
register,
handleSubmit,
@@ -30,20 +31,21 @@ export const UserCreate = () => {
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<h2>Opprett ny</h2>
<form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
<label>Fornavn</label>
<input {...register("firstName", { required: true })} />
{errors.firstName && <span>This field is required</span>}
<br />
<label>Etternavn</label>
<input {...register("lastName", { required: true })} />
{errors.lastName && <span>This field is required</span>}
<br />
<label>E-post</label>
<input type="email" {...register("email", { required: true })} />
{errors.email && <span>This field is required</span>}
<br />
<input type="submit" disabled={creating} />
<input type="submit" disabled={creating} className="btn-primary" />
</form>
</div>
);
};
export default UserCreate;
@@ -1,13 +1,17 @@
.userList {
display: grid;
height: 400px;
grid-template-columns: repeat(12, 100px);
height: 800px;
padding-top: 2rem;
grid-template-columns: repeat(5, 220px);
overflow-y: auto;
row-gap: 16px;
column-gap: 16px;
row-gap: 24px;
}
.amountSection {
margin-top: 32px;
}
.checkout {
margin-top: 32px;
}
+6 -5
View File
@@ -1,10 +1,10 @@
import React from "react";
import { UserCard } from "../../components/UserCard";
import { UserCard } from "../users/UserCard";
import styles from "./Deposit.module.css";
import { useDeposit } from "./useDeposit";
export const Deposit: React.FC = () => {
const Deposit = (): JSX.Element => {
const {
loading,
selectedUser,
@@ -33,7 +33,7 @@ export const Deposit: React.FC = () => {
placeholder="Filtrér på navn"
/>
{userQuery !== "" && (
<button className="btn btn-link" type="button" onClick={() => setUserQuery("")}>
<button type="button" onClick={() => setUserQuery("")}>
Vis alle
</button>
)}
@@ -55,13 +55,14 @@ export const Deposit: React.FC = () => {
<input name="amount" value={amount} onChange={(event) => setAmount(event.target.value)} placeholder="0" />
</section>
<section className={styles.checkout}>
<button type="button" className="btn btn-primary btn-lg" onClick={onSubmit}>
<button type="button" onClick={onSubmit} className="primary">
Sett inn
</button>{" "}
<button type="button" className="btn btn-outline-secondary btn-lg" onClick={reset}>
<button type="button" onClick={reset}>
Start på nytt
</button>
</section>
</div>
);
};
export default Deposit;
+27
View File
@@ -3,3 +3,30 @@
gap: 1rem;
grid-template-columns: repeat(2, 1fr);
}
.disabled,
.disabled a:link {
color: rgba(121, 152, 238, 0.5);
}
.pagination {
display: flex;
align-items: flex-end;
padding: 0;
margin: 0;
margin-top: 1.5rem;
gap: 0 1rem;
list-style: none;
}
.pagination a {
padding: 0.4rem 0.8rem;
border: 1px solid var(--border);
margin-top: 1rem;
border-radius: 4px;
}
.pagination a:hover {
color: white;
}
.disabled a:hover {
color: rgba(121, 152, 238, 0.5);
}
+18 -21
View File
@@ -9,7 +9,7 @@ import styles from "./Log.module.css";
const PAGE_SIZE = 10;
export const Log = () => {
const Log = () => {
const types = {
orderOffset: Number,
transactionOffset: Number,
@@ -34,7 +34,7 @@ export const Log = () => {
<div className={styles.log}>
<div>
<h2>Ordre</h2>
<table className="table">
<table>
<thead>
<tr>
<th scope="col">Når</th>
@@ -61,29 +61,27 @@ export const Log = () => {
</tbody>
</table>
<nav aria-label="Order pagination">
<ul className="pagination">
<li className={clsx("page-item", { disabled: orderOffset <= 0 })}>
<ul className={styles.pagination}>
<li className={clsx({ [styles.disabled]: orderOffset <= 0 })}>
<a
href="#"
className="page-link"
onClick={(e) => {
e.preventDefault();
setParams({ orderOffset: orderOffset - PAGE_SIZE });
setParams({ orderOffset: Math.max(0, orderOffset - PAGE_SIZE) });
}}
>
Previous
⬅
</a>
</li>
<li className="page-item">
<li>
<a
href="#"
className="page-link"
onClick={(e) => {
e.preventDefault();
setParams({ orderOffset: orderOffset + PAGE_SIZE });
setParams({ orderOffset: Math.max(0, orderOffset + PAGE_SIZE) });
}}
>
Next
➡
</a>
</li>
</ul>
@@ -91,7 +89,7 @@ export const Log = () => {
</div>
<div>
<h2>Transaksjoner</h2>
<table className="table">
<table>
<thead>
<tr>
<th scope="col">Når</th>
@@ -112,29 +110,27 @@ export const Log = () => {
</tbody>
</table>
<nav aria-label="Transaction pagination">
<ul className="pagination">
<li className={clsx("page-item", { disabled: transactionOffset <= 0 })}>
<ul className={styles.pagination}>
<li className={clsx({ [styles.disabled]: transactionOffset <= 0 })}>
<a
href="#"
className="page-link"
onClick={(e) => {
e.preventDefault();
setParams({ transactionOffset: transactionOffset - PAGE_SIZE });
setParams({ transactionOffset: Math.max(0, transactionOffset - PAGE_SIZE) });
}}
>
Previous
⬅
</a>
</li>
<li className="page-item">
<li>
<a
href="#"
className="page-link"
onClick={(e) => {
e.preventDefault();
setParams({ transactionOffset: transactionOffset + PAGE_SIZE });
setParams({ transactionOffset: Math.max(transactionOffset + PAGE_SIZE) });
}}
>
Next
➡
</a>
</li>
</ul>
@@ -143,3 +139,4 @@ export const Log = () => {
</div>
);
};
export default Log;
@@ -0,0 +1,51 @@
.product {
display: flex;
border: none;
background: rgb(35, 15, 41);
border-radius: 4px;
transition: background-color 0.1s ease-out, border 0.1s ease-out;
position: relative;
align-items: flex-start;
padding: 1rem;
}
.product:hover,
.active.product {
background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
}
.productText {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 1.2rem;
text-shadow: none;
color: white;
}
.amount {
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
right: 0;
padding: 12px 12px 8px 8px;
border-left: 1px solid transparent;
color: transparent;
font-size: 2rem;
font-weight: bold;
line-height: 1;
background-color: transparent;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
display: none;
}
.active .amount {
display: block;
color: var(--background);
text-shadow: none;
border-left-color: rgb(35, 15, 41);
background-color: var(--sun-end);
}
@@ -0,0 +1,22 @@
import clsx from "clsx";
import React, { MouseEventHandler } from "react";
import { Product } from "../../types";
import styles from "./ProductCard.module.css";
type ProductCardProps = {
product: Omit<Product, "userCounts">;
onProduct: MouseEventHandler;
active: boolean;
amount: number;
};
export const ProductCard = ({ product, onProduct, amount, active = false }: ProductCardProps): JSX.Element => {
const classes = clsx(styles.product, { [styles.active]: active });
return (
<button className={classes} type="button" onClick={onProduct} title={product.name}>
<span className={styles.amount}>{amount}</span>
<span className={styles.productText}>{product.name}</span>
</button>
);
};
@@ -1,14 +1,21 @@
.userList {
display: grid;
grid-template-columns: repeat(12, 128px);
row-gap: 16px;
grid-template-columns: repeat(5, 220px);
column-gap: 16px;
row-gap: 24px;
}
.showAll {
margin-top: 1rem;
}
.productList {
display: grid;
grid-template-columns: repeat(8, 150px);
row-gap: 16px;
grid-template-columns: repeat(5, 220px);
column-gap: 16px;
row-gap: 24px;
}
.productListSection {
margin-top: 2rem;
}
@@ -22,7 +29,6 @@
min-width: 200px;
padding: 4px 8px;
margin-right: 8px;
background: black;
color: white;
font-size: 1.8rem;
}
+10 -9
View File
@@ -1,22 +1,22 @@
import React from "react";
import { ProductCard } from "../../components/ProductCard";
import { UserCard } from "../../components/UserCard";
import { OrderLineInput, Product } from "../../types";
import { UserCard } from "../users/UserCard";
import { ProductCard } from "./ProductCard";
import styles from "./Register.module.css";
import { useRegister } from "./useRegister";
const USER_EXTERNAL = "external";
export const Register: React.FC = () => {
const Register = (): JSX.Element => {
const {
users,
products,
loading,
showAll,
setShowAll,
setSelectedUser,
selectedUser,
toggleSelectedUser,
addToOrder,
order,
total,
@@ -36,19 +36,19 @@ export const Register: React.FC = () => {
<UserCard
key={USER_EXTERNAL}
user={null}
onUser={() => setSelectedUser(USER_EXTERNAL)}
onUser={() => toggleSelectedUser(USER_EXTERNAL)}
active={selectedUser == USER_EXTERNAL}
/>
{users.map((user) => (
<UserCard
key={user.id}
user={user}
onUser={() => setSelectedUser(user.id)}
onUser={() => toggleSelectedUser(user.id)}
active={selectedUser == user.id}
/>
))}
</div>
<button type="button" className="btn btn-link" onClick={() => setShowAll(!showAll)}>
<button type="button" onClick={() => setShowAll(!showAll)} className={styles.showAll}>
{showAll ? "Skjul gamliser" : "Vis alle"}
</button>
</section>
@@ -70,13 +70,14 @@ export const Register: React.FC = () => {
<span className={styles.total}>
Totalt: <span>{total}</span>
</span>
<button type="button" className="btn btn-primary btn-lg" onClick={onSubmit}>
<button type="button" onClick={onSubmit} className="primary">
Kjøp
</button>{" "}
<button type="button" className="btn btn-outline-secondary btn-lg" onClick={reset}>
<button type="button" onClick={reset}>
Start på nytt
</button>
</section>
</div>
);
};
export default Register;
@@ -38,6 +38,10 @@ export const useRegister = () => {
setSelectedUser("");
};
const toggleSelectedUser = (userId: string) => {
setSelectedUser(selectedUser == userId ? "" : userId);
};
const addToOrder = (productId: string) => {
let added = false;
const newOrder = order.map((orderLine: OrderLineInput) => {
@@ -94,6 +98,7 @@ export const useRegister = () => {
setShowAll,
setSelectedUser,
selectedUser,
toggleSelectedUser,
addToOrder,
order,
total,
+2 -1
View File
@@ -35,7 +35,7 @@ const OrderBar = ({ data }: { data: BarDatum[] }) => (
/>
);
export const OrderStats = () => {
const OrderStats = () => {
const { data, loading } = useQuery(OrderStatsDocument);
return (
@@ -77,3 +77,4 @@ export const OrderStats = () => {
</StatsView>
);
};
export default OrderStats;
+2 -1
View File
@@ -11,7 +11,7 @@ type UserCount = {
count: number;
};
export const ProductStats = () => {
const ProductStats = () => {
const { data, loading } = useQuery(AllProductStatsDocument, {
variables: { active: true },
});
@@ -38,3 +38,4 @@ export const ProductStats = () => {
</StatsView>
);
};
export default ProductStats;
@@ -4,6 +4,26 @@
gap: 1rem;
grid-template-columns: repeat(3, 1fr);
}
.statsNav {
margin-bottom: 1rem;
}
.statsNav ul {
display: flex;
padding: 0;
margin: 0;
gap: 0 1rem;
list-style: none;
}
.statsNav a {
padding: 0.4rem 0.8rem;
border: 1px solid transparent;
}
.active.active {
color: white;
}
.statsNav a:hover {
border: 1px solid var(--border);
border-radius: 4px;
}
+2 -1
View File
@@ -5,7 +5,7 @@ import React from "react";
import { ProductStatsDocument } from "../../types";
import { StatsView } from "./StatsView";
export const Stats = () => {
const Stats = () => {
const { data, loading } = useQuery(ProductStatsDocument);
const series: any[] = [];
@@ -68,3 +68,4 @@ export const Stats = () => {
</StatsView>
);
};
export default Stats;
+11 -9
View File
@@ -1,34 +1,36 @@
import clsx from "clsx";
import React from "react";
import { NavLink } from "react-router-dom";
import styles from "./Stats.module.css";
const navLinkStyles = ({ isActive }: { isActive: boolean }) => (isActive ? "nav-link active" : "nav-link");
const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive });
export const StatsNav = () => (
<div className={styles.statsNav}>
<ul className="nav nav-tabs">
<li className="nav-item">
<nav className={styles.statsNav}>
<ul>
<li>
<NavLink className={navLinkStyles} to="/stats" end>
Oversikt
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink className={navLinkStyles} to="/stats/products">
Produkter
</NavLink>
</li>
<li className="nav-item">
<li>
<NavLink className={navLinkStyles} to="/stats/orders">
Ordre
</NavLink>
</li>
<li className="nav-item">
<NavLink className="nav-link disabled" to="/stats/users" title="TODO">
<li>
<NavLink className="disabled" to="/stats/users" title="TODO">
Folk
</NavLink>
</li>
</ul>
</div>
</nav>
);
export const StatsView = ({ children, loading }: { children: React.ReactNode; loading: boolean }) => (
@@ -0,0 +1,61 @@
.user {
display: flex;
align-items: center;
padding: 0;
border: none;
border-left: 4px solid transparent;
background: rgb(35, 15, 41);
border-radius: 4px;
color: var(--text);
transition: background-color 0.1s ease-out, border 0.1s ease-out;
max-height: 82px;
}
.user:hover {
cursor: pointer;
}
.user:hover,
.active.user {
background-color: var(--background);
box-shadow: 0 0 4px var(--background), 0 0 11px var(--background), 0 0 19px var(--background);
}
.active.user {
border-left: 4px solid var(--sun)
}
.avatar {
margin-left: 1rem;
width: 50px;
height: 50px;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
.avatarWrap {
filter: drop-shadow(0 0 4px var(--background)) drop-shadow(0 0 11px var(--background))
drop-shadow(0 0 19px var(--background));
}
.noAvatar {
margin-left: 1rem;
display: block;
width: 50px;
height: 50px;
background-color: black;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
content: "";
}
.meta {
display: flex;
padding: 1rem;
flex-direction: column;
align-items: flex-start;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 1.2rem;
text-shadow: none;
}
.balance {
color: var(--sun)
}
+34
View File
@@ -0,0 +1,34 @@
import clsx from "clsx";
import React, { MouseEventHandler } from "react";
import { AllUsersQuery } from "../../types";
import styles from "./UserCard.module.css";
type UserCardProps = {
user: AllUsersQuery["allUsers"][0] | null;
onUser: MouseEventHandler;
active: boolean;
};
/** If user is null, then it's the special external user */
export const UserCard = ({ user, onUser, active = false }: UserCardProps): JSX.Element => {
const classes = clsx(styles.user, { [styles.active]: active });
return (
<button className={classes} onClick={onUser} type="button" title={user?.firstName || "Ekstern"}>
<div className={styles.avatarWrap}>
{user?.profile?.image && (
<img src={user.profile?.image} alt={`Profile for ${user.firstName}`} className={styles.avatar} />
)}
{!user?.profile?.image && (
<span className={styles.avatar}>
<span className={styles.noAvatar} />
</span>
)}
</div>
<div className={styles.meta}>
{user?.firstName || "Ekstern"}
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
</div>
</button>
);
};
+16 -12
View File
@@ -3,16 +3,19 @@ import { Line } from "@nivo/line";
import React from "react";
import { useParams } from "react-router-dom";
import { GQLError } from "../../components/GQLError";
import { User, UserDetailDocument, UserProfile } from "../../types";
import { formatMonth, formatTime } from "../../utils/time";
import styles from "./UserDetail.module.css";
export const UserDetail = () => {
const UserDetail = () => {
const { userId } = useParams();
const { data, loading } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
const { data, loading, error } = useQuery(UserDetailDocument, { variables: { userId: userId || "" }, skip: !userId });
if (loading) return <div>Loading...</div>;
if (error) return <GQLError error={error} />;
const user = data?.user as User;
const profile = user?.profile as UserProfile;
// FIXME: types
@@ -30,26 +33,26 @@ export const UserDetail = () => {
<h1>
{profile.image && <img src={profile.image} alt={`${user.firstName}s profile picture`} />} {user.firstName}
</h1>
<div className="card">
<div className="card-header">Stats</div>
<ul className="list-group list-group-flush">
<li className="list-group-item">
<div>
<div>Stats</div>
<ul>
<li>
Saldo: <strong>{profile.balance.toLocaleString("no")} kr</strong>
</li>
<li className="list-group-item">
<li>
Siste kjøp: <strong>{formatTime(profile.lastPurchaseDate)}</strong>
</li>
<li className="list-group-item">
<li>
Kjøp totalt: <strong>{profile.orderSumTotal.toLocaleString("no")} kr</strong>
</li>
</ul>
</div>
<br />
<div className="card">
<div className="card-header">Top 5 måneder</div>
<ul className="list-group list-group-flush">
<div>
<div>Top 5 måneder</div>
<ul>
{profile.topMonths.map((topMonth) => (
<li key={topMonth.period} className="list-group-item">
<li key={topMonth.period}>
{formatMonth(topMonth.period)}: <strong>{topMonth.count}</strong> kjøp på totalt{" "}
<strong>{topMonth.sum} kr</strong>
</li>
@@ -108,3 +111,4 @@ export const UserDetail = () => {
</div>
);
};
export default UserDetail;
+5 -3
View File
@@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
import { AllUsersDocument } from "../../types";
import { activeUserFilter } from "../../utils/users";
export const UserList = () => {
const UserList = () => {
const { data, loading } = useQuery(AllUsersDocument);
const [showAll, setShowAll] = useState(false);
@@ -15,7 +15,8 @@ export const UserList = () => {
const users = showAll ? allUsers : allUsers.filter((u) => activeUserFilter(u));
return (
<div>
<button type="button" className="btn btn-link" onClick={() => setShowAll(!showAll)}>
<h2>Folk</h2>
<button type="button" onClick={() => setShowAll(!showAll)}>
{showAll ? "Skjul gamliser" : "Vis alle"}
</button>
<ul>
@@ -28,9 +29,10 @@ export const UserList = () => {
))}
</ul>
<br />
<Link to="/users/create" className="btn btn-primary">
<Link to="/users/create" className="btn-primary">
Opprett ny
</Link>
</div>
);
};
export default UserList;
+145
View File
@@ -0,0 +1,145 @@
/* Custom props */
:root {
--brand: rgb(243, 33, 61);
--background: rgb(70, 30, 82);
--text: rgb(255, 255, 255);
--sun: rgb(221, 81, 127);
--sun-end: rgb(230, 142, 54);
--border: rgb(85, 109, 200);
--interlace: rgb(121, 152, 238);
--font-family: "IBM Plex Mono", monospace;
}
/* Resets */
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
/* Body and interitable styles */
body {
background: linear-gradient(rgba(0, 0, 0, 0.8) 60%, var(--background) 100%);
background-color: var(--background);
color: var(--text);
font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
}
/* Links */
a:link {
color: var(--interlace);
text-decoration: none;
}
a:visited {
color: var(--interlace);
}
a:active {
color: var(--interlace);
}
a:hover {
color: var(--text);
}
h1,
h2,
h3,
h4,
h5 {
color: var(--sun);
}
a.btn-primary {
padding: 0.4rem 1rem;
border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
color: var(--text);
}
/* Buttons */
button {
padding: 0.4rem 1rem;
border: 1px solid var(--border);
background: transparent;
border-radius: 4px;
color: var(--border);
font-family: var(--font-family);
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
}
button:hover {
color: var(--text);
cursor: pointer;
}
button.primary {
border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
color: var(--text);
}
/* Inputs */
input {
padding: 0.4rem 0.4rem;
border: 1px solid var(--border);
background: transparent;
border-radius: 4px;
color: var(--text);
font-family: var(--font-family);
font-size: 1.5rem;
text-shadow: 0 0 4px currentColor, 0 0 11px currentColor, 0 0 19px currentColor;
}
input:focus {
background: rgba(85, 109, 200, 0.1);
outline: none;
}
input.btn-primary {
padding: 0.4rem 1rem;
border: 1px solid transparent;
background: linear-gradient(var(--sun) 30%, var(--sun-end) 100%);
border-radius: 4px;
box-shadow: 0 0 4px var(--sun), 0 0 11px var(--sun), 0 0 19px var(--sun);
color: var(--text);
}
::placeholder {
color: var(--border);
}
/* Tables */
table {
border: 1px solid var(--border);
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0.5rem 0.8rem;
border: 1px solid var(--border);
}
/* Fonts*/
@font-face {
font-family: "Rocket Clouds DEMO";
font-style: normal;
font-weight: normal;
src: url("assets/rocket-clouds.ttf") format("truetype");
}
@font-face {
font-family: "Rocket Rinder";
font-style: normal;
font-weight: normal;
src: url("assets/rocket-rinder.ttf") format("truetype");
}
+2
View File
@@ -3,6 +3,8 @@ import ReactDOM from "react-dom";
import App from "./App";
import "./global.css";
ReactDOM.render(
<React.StrictMode>
<App />
+1 -2
View File
@@ -4,11 +4,10 @@ import svgr from "vite-plugin-svgr";
// https://vitejs.dev/config/
export default defineConfig({
base: process.env.NODE_ENV !== "production" ? "/" : "/static/modern/",
plugins: [svgr()],
define: {
__DEV__: process.env.NODE_ENV !== "production",
},
envDir: "..",
build: { outDir: "../static/modern", emptyOutDir: true },
build: { outDir: "../static", emptyOutDir: false },
});
-9
View File
@@ -1,9 +0,0 @@
import strawberry
import strawberry_django
from main.api.types.inventory import Transaction, TransactionOrdering
@strawberry.type
class InventoryQueries:
transaction_list: list[Transaction] = strawberry_django.field(pagination=True, order=TransactionOrdering)
-20
View File
@@ -1,20 +0,0 @@
import strawberry
from main.api.mutations.shop import ShopMutations
from main.api.mutations.users import UsersMutations
from main.api.queries.inventory import InventoryQueries
from main.api.queries.shop import ShopQueries
from main.api.queries.users import UsersQueries
@strawberry.type
class Query(InventoryQueries, ShopQueries, UsersQueries):
pass
@strawberry.type
class Mutation(ShopMutations, UsersMutations):
pass
schema = strawberry.Schema(query=Query, mutation=Mutation)
-11
View File
@@ -1,11 +0,0 @@
from django.urls import re_path
from main.views.reports import inventory, inventory_add, report
urlpatterns = [
# TODO: migrate to SPA
# Reports
re_path(r"^inventory/$", inventory, name="inventory"),
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
re_path(r"^report$", report, name="report"),
]
View File
Whitespace-only changes.
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbssite.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bbs.settings")
from django.core.management import execute_from_command_line
Generated
+294 -49
View File
@@ -31,6 +31,25 @@ docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "backports.cached-property"
version = "1.0.1"
description = "cached_property() - computed once per instance, cached as attribute"
category = "main"
optional = false
python-versions = ">=3.6.0"
[[package]]
name = "backports.zoneinfo"
version = "0.2.1"
description = "Backport of the standard library zoneinfo module"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
tzdata = ["tzdata"]
[[package]]
name = "black"
version = "22.1.0"
@@ -53,14 +72,6 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cached-property"
version = "1.5.2"
description = "A decorator for caching properties in classes."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "certifi"
version = "2021.10.8"
@@ -96,6 +107,22 @@ category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cssselect2"
version = "0.4.1"
description = "cssselect2"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
tinycss2 = "*"
webencodings = "*"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "distlib"
version = "0.3.4"
@@ -106,16 +133,17 @@ python-versions = "*"
[[package]]
name = "django"
version = "3.2.12"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
version = "4.0.2"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.8"
[package.dependencies]
asgiref = ">=3.3.2,<4"
pytz = "*"
asgiref = ">=3.4.1,<4"
"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""}
sqlparse = ">=0.2.2"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
@@ -143,17 +171,30 @@ python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
[[package]]
name = "django-rename-app"
version = "0.1.3"
description = "A Django Management Command to rename existing Django Applications."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
django = "*"
[[package]]
name = "easy-thumbnails"
version = "2.7.2"
version = "2.8.1"
description = "Easy thumbnails for Django"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.6"
[package.dependencies]
django = ">=1.11,<4.0"
django = ">=2.2"
pillow = "*"
reportlab = "*"
svglib = "*"
[[package]]
name = "filelock"
@@ -222,6 +263,20 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
[[package]]
name = "lxml"
version = "4.8.0"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
[package.extras]
cssselect = ["cssselect (>=0.7)"]
html5 = ["html5lib"]
htmlsoup = ["beautifulsoup4"]
source = ["Cython (>=0.29.7)"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
@@ -275,7 +330,7 @@ python-versions = ">=3.7"
[[package]]
name = "platformdirs"
version = "2.5.0"
version = "2.5.1"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
@@ -398,14 +453,6 @@ python-versions = "*"
[package.dependencies]
six = ">=1.4.0"
[[package]]
name = "pytz"
version = "2021.3"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyyaml"
version = "6.0"
@@ -414,6 +461,20 @@ category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "reportlab"
version = "3.6.7"
description = "The Reportlab Toolkit"
category = "main"
optional = false
python-versions = ">=3.6, <4"
[package.dependencies]
pillow = ">=4.0.0"
[package.extras]
rlpycairo = ["rlPyCairo (>=0.0.5)"]
[[package]]
name = "sentinel"
version = "0.3.0"
@@ -427,7 +488,7 @@ varname = ["varname (>=0.1)"]
[[package]]
name = "sentry-sdk"
version = "1.4.3"
version = "1.5.5"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
@@ -449,6 +510,7 @@ flask = ["flask (>=0.11)", "blinker (>=1.1)"]
httpx = ["httpx (>=0.16.0)"]
pure_eval = ["pure-eval", "executing", "asttokens"]
pyspark = ["pyspark (>=2.4.4)"]
quart = ["quart (>=0.16.1)", "blinker (>=1.1)"]
rq = ["rq (>=0.6)"]
sanic = ["sanic (>=0.8)"]
sqlalchemy = ["sqlalchemy (>=1.2)"]
@@ -472,14 +534,14 @@ python-versions = ">=3.5"
[[package]]
name = "strawberry-graphql"
version = "0.93.23"
version = "0.97.0"
description = "A library for creating GraphQL APIs"
category = "main"
optional = false
python-versions = ">=3.7,<4.0"
[package.dependencies]
cached-property = ">=1.5.2,<2.0.0"
"backports.cached-property" = ">=1.0.1,<2.0.0"
click = ">=7.0,<9.0"
graphql-core = ">=3.1.0,<3.2.0"
pygments = ">=2.3,<3.0"
@@ -490,7 +552,7 @@ typing_extensions = ">=3.7.4,<5.0.0"
[package.extras]
asgi = ["starlette (>=0.13.6,<0.17.0)"]
debug-server = ["starlette (>=0.13.6,<0.17.0)", "uvicorn (>=0.11.6,<0.17.0)"]
debug-server = ["starlette (>=0.13.6,<0.17.0)", "uvicorn (>=0.11.6,<0.18.0)"]
django = ["Django (>=2.2,<4)", "Django (>=2.2,<5)", "asgiref (>=3.2,<4.0)"]
flask = ["flask (>=1.1,<2.0)"]
opentelemetry = ["opentelemetry-api (<2)", "opentelemetry-sdk (<2)"]
@@ -512,6 +574,35 @@ python-versions = ">=3.7,<4.0"
Django = ">=3.0"
strawberry-graphql = ">=0.69.0"
[[package]]
name = "svglib"
version = "1.2.1"
description = "A pure-Python library for reading and converting SVG"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cssselect2 = ">=0.2.0"
lxml = "*"
reportlab = "*"
tinycss2 = ">=0.6.0"
[[package]]
name = "tinycss2"
version = "1.1.1"
description = "A tiny CSS parser"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
webencodings = ">=0.4"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["pytest", "pytest-cov", "pytest-flake8", "pytest-isort", "coverage"]
[[package]]
name = "toml"
version = "0.10.2"
@@ -536,6 +627,14 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "tzdata"
version = "2021.5"
description = "Provider of IANA time zone data"
category = "main"
optional = false
python-versions = ">=2"
[[package]]
name = "urllib3"
version = "1.26.8"
@@ -567,10 +666,18 @@ six = ">=1.9.0,<2"
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
[[package]]
name = "webencodings"
version = "0.5.1"
description = "Character encoding aliases for legacy web content"
category = "main"
optional = false
python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "3fa14cb7bc28f810082b1aa4604a74060ec4ea32e63864f91a005aa37ea73c8c"
python-versions = "~3.8"
content-hash = "7d014e036d7cdddaa215ab1b17e7aaff3a3d4bbf34804fb52e1047bbe00607f4"
[metadata.files]
asgiref = [
@@ -585,6 +692,28 @@ attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
"backports.cached-property" = [
{file = "backports.cached-property-1.0.1.tar.gz", hash = "sha256:1a5ef1e750f8bc7d0204c807aae8e0f450c655be0cf4b30407a35fd4bb27186c"},
{file = "backports.cached_property-1.0.1-py3-none-any.whl", hash = "sha256:687b5fe14be40aadcf547cae91337a1fdb84026046a39370274e54d3fe4fb4f9"},
]
"backports.zoneinfo" = [
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"},
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"},
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"},
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"},
{file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"},
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"},
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"},
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"},
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"},
{file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"},
{file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"},
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"},
{file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"},
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"},
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
{file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
]
black = [
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
@@ -610,10 +739,6 @@ black = [
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
]
cached-property = [
{file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
{file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
@@ -630,13 +755,17 @@ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
cssselect2 = [
{file = "cssselect2-0.4.1-py3-none-any.whl", hash = "sha256:2f4a9f20965367bae459e3bb42561f7927e0cfe5b7ea1692757cf67ef5d7dace"},
{file = "cssselect2-0.4.1.tar.gz", hash = "sha256:93fbb9af860e95dd40bf18c3b2b6ed99189a07c0f29ba76f9c5be71344664ec8"},
]
distlib = [
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
{file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
]
django = [
{file = "Django-3.2.12-py3-none-any.whl", hash = "sha256:9b06c289f9ba3a8abea16c9c9505f25107809fb933676f6c891ded270039d965"},
{file = "Django-3.2.12.tar.gz", hash = "sha256:9772e6935703e59e993960832d66a614cf0233a1c5123bc6224ecc6ad69e41e2"},
{file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"},
{file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"},
]
django-cors-headers = [
{file = "django-cors-headers-3.11.0.tar.gz", hash = "sha256:eb98389bf7a2afc5d374806af4a9149697e3a6955b5a2dc2bf049f7d33647456"},
@@ -646,9 +775,13 @@ django-extensions = [
{file = "django-extensions-3.1.5.tar.gz", hash = "sha256:28e1e1bf49f0e00307ba574d645b0af3564c981a6dfc87209d48cb98f77d0b1a"},
{file = "django_extensions-3.1.5-py3-none-any.whl", hash = "sha256:9238b9e016bb0009d621e05cf56ea8ce5cce9b32e91ad2026996a7377ca28069"},
]
django-rename-app = [
{file = "django_rename_app-0.1.3-py2.py3-none-any.whl", hash = "sha256:bba31d33de4614124e2cd2f22fcea07b2affbbb2cf877a26893e0c15465fbfab"},
{file = "django_rename_app-0.1.3.tar.gz", hash = "sha256:195d2fa09eec8ed57577f7bf57a3c6944bdb6447fe47c07be0e575fb2e325742"},
]
easy-thumbnails = [
{file = "easy-thumbnails-2.7.2.tar.gz", hash = "sha256:a7dd9cf97efaf70ba5d76484a962f08ba65b31f1681bc417257743650e9e8a8a"},
{file = "easy_thumbnails-2.7.2-py3-none-any.whl", hash = "sha256:9d7ed56a9b57aba7a8d0e03dc3b8507ca51aab5e0ed0bc705c3261ff1f659192"},
{file = "easy-thumbnails-2.8.1.tar.gz", hash = "sha256:1a283fe8a3569c3feab4605e8279929d75c85c1151b2fd627f95b1863b5fc6c2"},
{file = "easy_thumbnails-2.8.1-py3-none-any.whl", hash = "sha256:5f59f722508469d482d8d76b99c7b9e6c1abbce19aefd5932dc6e1b9eacd2473"},
]
filelock = [
{file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
@@ -674,6 +807,69 @@ isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
lxml = [
{file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"},
{file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"},
{file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"},
{file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"},
{file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"},
{file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"},
{file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"},
{file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"},
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"},
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"},
{file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"},
{file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"},
{file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"},
{file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"},
{file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"},
{file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"},
{file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"},
{file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"},
{file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"},
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"},
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"},
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"},
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"},
{file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"},
{file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"},
{file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"},
{file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"},
{file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"},
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"},
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"},
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"},
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"},
{file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"},
{file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"},
{file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"},
{file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"},
{file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"},
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"},
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"},
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"},
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"},
{file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"},
{file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"},
{file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"},
{file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"},
{file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"},
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"},
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"},
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"},
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"},
{file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"},
{file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"},
{file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"},
{file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"},
{file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"},
{file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"},
{file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"},
{file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"},
{file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"},
{file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"},
{file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@@ -735,8 +931,8 @@ pillow = [
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
]
platformdirs = [
{file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
{file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@@ -773,10 +969,6 @@ python-dateutil = [
python-multipart = [
{file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"},
]
pytz = [
{file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
{file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -812,13 +1004,51 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
reportlab = [
{file = "reportlab-3.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fd5bc5d0b56df4cafb70b5ba8ec0bb19921574d2f99fbf623c3a215fcaa79aba"},
{file = "reportlab-3.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd4d53e5d09f9912df86746330148d739ad837424383ea0b99486ac38e5edaa2"},
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d72e9f2d5cf20378b1c6a8ea3589728496744677332f0688dc415a8a5c91b7"},
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d402cd8cdd1b9b8631b6d7811c6bb788aa3caa3f38b300e9d4a5e12276d8863"},
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91d2952ca4264c145927303ec3c03cdf6b0b035688077b514ba37e8683c21ec"},
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80c9cdfed6b4bcca9dfd904d1d139b98975cd9ccb2558588307a0d53b5b6c8b4"},
{file = "reportlab-3.6.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1890f197f12f3c734bafc21cbf9ebe18164e780164997a0d717187db2a46adc"},
{file = "reportlab-3.6.7-cp310-cp310-win32.whl", hash = "sha256:4432abb4c61d914cbac22baa9172efe8036074cea539a12f3b37134055d38ccb"},
{file = "reportlab-3.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:47fa377b24044514807d539780c58bc5cec91fa4ea8700f3c150c61de358d91a"},
{file = "reportlab-3.6.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232ff75d29a1a5b3e8aa321eaec8e541afaf9704fd2e059127b816df3f2296f4"},
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf2e6e5b4d58ad1220228f5620064e13a53148775c28dcbe1c552f3f7e359389"},
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27088f0f10639f8ca27222264910c816aa3b089d590b4a1c01e9d1b0935835df"},
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8faa40a3f74bbf932bcda30ea8258511a3b6194460202a4720c1e4b64acb338"},
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec67e5bdf6cb0f3b410b1bfb75a41aeb39f89ffedd1fd5165fa87833ecbb48dd"},
{file = "reportlab-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:439f3b2eca084aad48f6231253a33469c8eac9fcd924d80b157cbe6b77699931"},
{file = "reportlab-3.6.7-cp37-cp37m-win32.whl", hash = "sha256:6bb42e672c1866a52412888cc07a91589a28a922100770c1f53569d3e7e3ad8f"},
{file = "reportlab-3.6.7-cp37-cp37m-win_amd64.whl", hash = "sha256:0e308e91d995beb35f0cef8c7c16d6c10c22ae937929c345f745caa6d371b7a9"},
{file = "reportlab-3.6.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449a03741f10454d64afc55180eeac91bb22f3560332c7e25df3dc7e884cbf22"},
{file = "reportlab-3.6.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3bda030877d3b683e4fb72fd114a6dad1e5b1ba83928ae914af5e0171d5bcff9"},
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7e5b0372f15331d806dd5847c4ec1f737b02438579a7e2dd798e9bf8c27c83e"},
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b1360785b73a52613712df48600d62788e0fb3ebd6e47fa52fb0dd498430893"},
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9462b443d9f0834d997039fc2648336ada3141dc96b146bcde85c81a6b3224"},
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:401b72f70fcac83c52853d17b0c1f1f9d93c6ceaafdd94b3c0e7b0bdfecca182"},
{file = "reportlab-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e28735a9a01124637d637a8513ffec74a34e5fdd3c3af595eb3e0217525e8e0"},
{file = "reportlab-3.6.7-cp38-cp38-win32.whl", hash = "sha256:a137df1507414115e3f0c8821514f450d9f9c973f2821dfa112ed797464fc93d"},
{file = "reportlab-3.6.7-cp38-cp38-win_amd64.whl", hash = "sha256:5013b0468712dc9fbd9ace297821d27677abf427ac32fe2b537d8876bfb41c65"},
{file = "reportlab-3.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891b2ad614b43d911f70bcdefe2366db8ca7e85e952ffa96255975c1636d46e4"},
{file = "reportlab-3.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fe319e0891e20fb04f038926ace02cf87f34fd223c478437572b5ed2b821f8af"},
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c0c5c0611c9b6b3d1aae0037efb303c57da6d1349830b83915a92bd0f5f246"},
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e703d862c1c85b5e377e3cf2b33763303deb80e799ad301e99d09ec81feecdb1"},
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13824c9363393ead2ed72907ecc40250c6d3489963627bd34e437e5accea2a1"},
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:51b222f2315d34a7201b1ea5d843715a2625107838de52979406d4dd5ab2f7f6"},
{file = "reportlab-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea5ca026695210cc3364328a9490b2f29ce97b99614950df8fb11b2680641008"},
{file = "reportlab-3.6.7-cp39-cp39-win32.whl", hash = "sha256:d5f5ab22f71433a665f50b24cced62298626753536a50db0c769c2fee07038e6"},
{file = "reportlab-3.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:06c8dbaa26c2bf63e7fcddb86f232ddaf7c57141072407d22b4e8cbfcc5348e4"},
{file = "reportlab-3.6.7.tar.gz", hash = "sha256:d035054724278129aba2a55c3edd6111b07e625ad1ff845e3ba2a5e4e203bbc8"},
]
sentinel = [
{file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"},
{file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"},
]
sentry-sdk = [
{file = "sentry-sdk-1.4.3.tar.gz", hash = "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828"},
{file = "sentry_sdk-1.4.3-py2.py3-none-any.whl", hash = "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"},
{file = "sentry-sdk-1.5.5.tar.gz", hash = "sha256:98fd155fa5d5fec1dbabed32a1a4ae2705f1edaa5dae4e7f7b62a384ba30e759"},
{file = "sentry_sdk-1.5.5-py2.py3-none-any.whl", hash = "sha256:3817274fba2498c8ebf6b896ee98ac916c5598706340573268c07bf2bb30d831"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -829,13 +1059,20 @@ sqlparse = [
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
]
strawberry-graphql = [
{file = "strawberry-graphql-0.93.23.tar.gz", hash = "sha256:061b1c54c69dd7e11cb2b7e7b474392e4b32943789e7ac0b47954fb965999d31"},
{file = "strawberry_graphql-0.93.23-py3-none-any.whl", hash = "sha256:b2a9a4af0a491e1626aafc420e484f551f89eacb13c0e48de9ab5dd8f7e5ca34"},
{file = "strawberry-graphql-0.97.0.tar.gz", hash = "sha256:0bd486111166da1d6e48a2c95de437096ca2ba28e2b2963b64a93136c54bef02"},
{file = "strawberry_graphql-0.97.0-py3-none-any.whl", hash = "sha256:2f304822c6ecc866e07f11618fbdf826676ab61af5f757794805ee1853504603"},
]
strawberry-graphql-django = [
{file = "strawberry-graphql-django-0.2.5.tar.gz", hash = "sha256:483d97c24ce440a1e3cb92ec3e1fdc0cf534d9afa79600396b0a27a5d9d97793"},
{file = "strawberry_graphql_django-0.2.5-py3-none-any.whl", hash = "sha256:dee8011bf074c618b7ddab0a92d0832b0ceea2dcb69ee422003a98e4a28c14d6"},
]
svglib = [
{file = "svglib-1.2.1.tar.gz", hash = "sha256:c77a0702fafd367c0fdca08ca1b7e1ee10058bde3bae252f49a3836e51e54519"},
]
tinycss2 = [
{file = "tinycss2-1.1.1-py3-none-any.whl", hash = "sha256:fe794ceaadfe3cf3e686b22155d0da5780dd0e273471a51846d0a02bc204fec8"},
{file = "tinycss2-1.1.1.tar.gz", hash = "sha256:b2e44dd8883c360c35dd0d1b5aad0b610e5156c2cb3b33434634e539ead9d8bf"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
@@ -848,6 +1085,10 @@ typing-extensions = [
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
]
tzdata = [
{file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"},
{file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"},
]
urllib3 = [
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
@@ -856,3 +1097,7 @@ virtualenv = [
{file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
{file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
]
webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
+7 -6
View File
@@ -1,5 +1,5 @@
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "bbssite.settings"
DJANGO_SETTINGS_MODULE = "bbs.settings"
[tool.black]
line-length = 120
@@ -17,17 +17,18 @@ authors = ["Nikolai R Kristiansen <nikolaik@gmail.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.9"
python = "~3.8"
django-extensions = "^3.1.3"
Django = "^3.2.8"
easy-thumbnails = "2.7.2"
Django = "^4.0.0"
easy-thumbnails = "^2.8.1"
gunicorn = "^20.1.0"
mysqlclient = "^2.0.3"
Pillow = "^9.0.0"
sentry-sdk = "1.4.3"
strawberry-graphql = "^0.93.1"
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 = "*"