rework-drf #4
@@ -5,6 +5,7 @@ ESI_USER_AGENT=
|
|||||||
|
|
||||||
REDIS_URL=
|
REDIS_URL=
|
||||||
REDIS_PORT=
|
REDIS_PORT=
|
||||||
|
REDIS_DB=
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
SQLITE_DB_PATH=
|
SQLITE_DB_PATH=
|
||||||
@@ -12,3 +13,7 @@ POSTGRES_HOST=
|
|||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
POSTGRES_USER=
|
POSTGRES_USER=
|
||||||
POSTGRES_DB=
|
POSTGRES_DB=
|
||||||
|
|
||||||
|
DRF_SECRET_KEY=
|
||||||
|
DRF_DEBUG=
|
||||||
|
ALLOWED_HOSTS=
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ ENV PYTHONDONTWRITEBYTECODE=1
|
|||||||
RUN adduser -u 5678 --disabled-password --gecos "" appuser
|
RUN adduser -u 5678 --disabled-password --gecos "" appuser
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY --chown=appuser:appuser manage.py /app/manage.py
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt
|
RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt
|
||||||
COPY --chown=appuser:appuser eveal /app/eveal
|
COPY --chown=appuser:appuser marbas /app/marbas
|
||||||
|
COPY --chown=appuser:appuser sde /app/sde
|
||||||
|
COPY --chown=appuser:appuser api /app/api
|
||||||
|
|
||||||
USER appuser
|
USER appuser
|
||||||
CMD ["uvicorn", "eveal.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
3
api/admin.py
Normal file
3
api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
api/apps.py
Normal file
6
api/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'api'
|
||||||
31
api/migrations/0001_initial.py
Normal file
31
api/migrations/0001_initial.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 4.2.6 on 2023-10-29 11:10
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sde', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Acquisition',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', models.IntegerField()),
|
||||||
|
('remaining', models.IntegerField()),
|
||||||
|
('price', models.FloatField()),
|
||||||
|
('date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('source', models.CharField(max_length=255)),
|
||||||
|
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='acquisitions', to='sde.sdetype')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='acquisitions', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
api/migrations/__init__.py
Normal file
0
api/migrations/__init__.py
Normal file
15
api/models.py
Normal file
15
api/models.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from django.db import models
|
||||||
|
from sde.models import SDEType
|
||||||
|
|
||||||
|
|
||||||
|
class Acquisition(models.Model):
|
||||||
|
type = models.ForeignKey(SDEType, related_name="acquisitions", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
quantity = models.IntegerField()
|
||||||
|
remaining = models.IntegerField()
|
||||||
|
|
||||||
|
price = models.FloatField()
|
||||||
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
|
source = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
user = models.ForeignKey("auth.User", related_name="acquisitions", on_delete=models.CASCADE)
|
||||||
22
api/serializers.py
Normal file
22
api/serializers.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from django.contrib.auth import models as auth_models
|
||||||
|
from rest_framework import serializers
|
||||||
|
from sde.models import SDEType
|
||||||
|
from api import models
|
||||||
|
|
||||||
|
|
||||||
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = auth_models.User
|
||||||
|
fields = ['id', 'username', 'email', 'groups']
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = auth_models.Group
|
||||||
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = models.Acquisition
|
||||||
|
fields = '__all__'
|
||||||
3
api/tests.py
Normal file
3
api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
14
api/urls.py
Normal file
14
api/urls.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from . import views
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'users', views.UserViewSet)
|
||||||
|
router.register(r'groups', views.GroupViewSet)
|
||||||
|
router.register(r'acquisitions', views.AcquisitionViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
path('types/search', views.custom_types_search, name='custom_types_search'),
|
||||||
|
path('reprocess/eval', views.reprocess_eval, name='custom_types_search'),
|
||||||
|
]
|
||||||
96
api/views.py
Normal file
96
api/views.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.contrib.auth import models as auth_models
|
||||||
|
from rest_framework import viewsets, permissions, settings
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from api import serializers, models
|
||||||
|
from sde import serializers as sde_serializers, models as sde_models
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows users to be viewed or edited.
|
||||||
|
"""
|
||||||
|
queryset = auth_models.User.objects.all().order_by('-date_joined')
|
||||||
|
serializer_class = serializers.UserSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows groups to be viewed or edited.
|
||||||
|
"""
|
||||||
|
queryset = auth_models.Group.objects.all()
|
||||||
|
serializer_class = serializers.GroupSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class AcquisitionViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
API endpoint that allows acquisitions to be viewed or edited.
|
||||||
|
"""
|
||||||
|
queryset = models.Acquisition.objects.all().order_by('-date')
|
||||||
|
serializer_class = serializers.AcquisitionSerializer
|
||||||
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def custom_types_search(request):
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for q in request.data:
|
||||||
|
conditions = Q()
|
||||||
|
for k in q.keys():
|
||||||
|
value = q[k]
|
||||||
|
|
||||||
|
token = k.split('___')
|
||||||
|
key, mods = token[0], token[1:]
|
||||||
|
|
||||||
|
cond = Q(**{key: value})
|
||||||
|
|
||||||
|
if "not" in mods:
|
||||||
|
cond = ~cond
|
||||||
|
|
||||||
|
conditions = conditions & cond
|
||||||
|
|
||||||
|
items.extend(sde_models.SDEType.objects.filter(conditions))
|
||||||
|
|
||||||
|
paginator = settings.api_settings.DEFAULT_PAGINATION_CLASS()
|
||||||
|
result_page = paginator.paginate_queryset(items, request)
|
||||||
|
serializer = sde_serializers.SDETypeSerializer(result_page, many=True)
|
||||||
|
return paginator.get_paginated_response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def reprocess_eval(request):
|
||||||
|
ep_mat = request.data.get("ep_mat")
|
||||||
|
ep_items = request.data.get("ep_items")
|
||||||
|
efficiency = request.data.get("efficiency", 0.55)
|
||||||
|
|
||||||
|
matprices = {item["typeID"]: {'sell': item["prices"]["sell"]["min"], 'buy': item["prices"]["buy"]["max"]} for item in ep_mat['items']}
|
||||||
|
|
||||||
|
item_reprocess = []
|
||||||
|
for rawitem in ep_items["items"]:
|
||||||
|
item = sde_models.SDEType.objects.get(id=rawitem["typeID"])
|
||||||
|
buy_reprocess = sell_reprocess = 0.0
|
||||||
|
for mat in item.typematerials.all():
|
||||||
|
try:
|
||||||
|
buy_reprocess += matprices[mat.material_type_id]['buy'] * mat.quantity/item.portionSize * efficiency
|
||||||
|
sell_reprocess += matprices[mat.material_type_id]['sell'] * mat.quantity/item.portionSize * efficiency
|
||||||
|
except KeyError as e:
|
||||||
|
JsonResponse({"error": f"No price for material {e}"})
|
||||||
|
item_reprocess.append({
|
||||||
|
"typeID": item.id,
|
||||||
|
"name": item.name,
|
||||||
|
"buy": rawitem["prices"]['buy']["max"],
|
||||||
|
"sell": rawitem["prices"]['sell']["min"],
|
||||||
|
"buy_reprocess": buy_reprocess,
|
||||||
|
"sell_reprocess": sell_reprocess,
|
||||||
|
})
|
||||||
|
|
||||||
|
paginator = settings.api_settings.DEFAULT_PAGINATION_CLASS()
|
||||||
|
result_page = paginator.paginate_queryset(item_reprocess, request)
|
||||||
|
return paginator.get_paginated_response(result_page)
|
||||||
@@ -1,22 +1,44 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
eveal:
|
migrations:
|
||||||
image: mabras:local
|
image: marbas:local
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
user: "1000:1000"
|
||||||
|
volumes:
|
||||||
|
- ./marbas:/app/marbas
|
||||||
|
- ./api:/app/api
|
||||||
|
- ./sde:/app/sde
|
||||||
|
- ./manage.py:/app/manage.py
|
||||||
|
command: sh -c "python manage.py makemigrations && python manage.py migrate"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
eveal:
|
||||||
|
image: marbas:local
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
|
user: "1000:1000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./eveal:/app/eveal
|
- ./marbas:/app/marbas
|
||||||
command: ["uvicorn", "eveal.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
- ./api:/app/api
|
||||||
|
- ./sde:/app/sde
|
||||||
|
- ./manage.py:/app/manage.py
|
||||||
|
# - ./static_eve:/app/static_eve
|
||||||
|
command: ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
migrations:
|
||||||
|
condition: service_completed_successfully
|
||||||
# elasticsearch:
|
# elasticsearch:
|
||||||
# condition: service_healthy
|
# condition: service_healthy
|
||||||
|
|
||||||
@@ -33,14 +55,14 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
volumes:
|
volumes:
|
||||||
- mabras_dbdata:/var/lib/postgresql/data
|
- marbas_dbdata:/var/lib/postgresql/data
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
|
test: pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
|
||||||
interval: 10s
|
interval: 1s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 3
|
retries: 10
|
||||||
|
|
||||||
# elasticsearch:
|
# elasticsearch:
|
||||||
# image: elasticsearch:latest
|
# image: elasticsearch:latest
|
||||||
@@ -58,4 +80,4 @@ services:
|
|||||||
# retries: 3
|
# retries: 3
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mabras_dbdata:
|
marbas_dbdata:
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import os
|
|
||||||
from sqlmodel import create_engine, Session
|
|
||||||
|
|
||||||
|
|
||||||
if os.getenv("POSTGRES_HOST"):
|
|
||||||
engine = create_engine(f"postgresql://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}/{os.getenv('POSTGRES_DB')}",
|
|
||||||
echo=False, future=True)
|
|
||||||
else:
|
|
||||||
sqlite_file_name = os.getenv("SQLITE_DB_PATH", "eveal.db")
|
|
||||||
engine = create_engine(f"sqlite:///{sqlite_file_name}", echo=True, future=True, connect_args={"check_same_thread": False})
|
|
||||||
|
|
||||||
def get_session():
|
|
||||||
db = Session(engine)
|
|
||||||
try:
|
|
||||||
yield db
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
33
eveal/esi.py
33
eveal/esi.py
@@ -1,33 +0,0 @@
|
|||||||
import datetime
|
|
||||||
import os
|
|
||||||
import redis
|
|
||||||
import pickle
|
|
||||||
from esy.client import ESIClient
|
|
||||||
from esy.auth import ESIAuthenticator
|
|
||||||
|
|
||||||
|
|
||||||
class ESICache(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self._r = redis.Redis(**kwargs)
|
|
||||||
# self._r = redis.StrictRedis(host=redis_url, port=redis_port, db=db)
|
|
||||||
|
|
||||||
def get(self, key):
|
|
||||||
# return pickle.loads(self._r[key])
|
|
||||||
return pickle.loads(self._r.get(key))
|
|
||||||
|
|
||||||
def set(self, key, data, cached_until: datetime.datetime):
|
|
||||||
self._r.set(key, pickle.dumps(data), ex=cached_until - datetime.datetime.now(datetime.timezone.utc))
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
# return item in self._r
|
|
||||||
return self._r.exists(item)
|
|
||||||
|
|
||||||
|
|
||||||
esi_client_id = os.getenv('ESI_CLIENT_ID')
|
|
||||||
esi_secret_key = os.getenv('ESI_SECRET_KEY')
|
|
||||||
|
|
||||||
esi_cache = ESICache(host=os.getenv("REDIS_URL"), port=int(os.getenv("REDIS_PORT")), db="0",
|
|
||||||
password=os.getenv("REDIS_PASSWD"))
|
|
||||||
|
|
||||||
esi_client = ESIClient.get_client(user_agent=os.getenv('ESI_USER_AGENT'), cache=esi_cache)
|
|
||||||
esi_auth = ESIAuthenticator()
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends, Path, Query
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from typing import List, Annotated, Tuple, Literal, Dict, TypedDict
|
|
||||||
from sqlmodel import SQLModel, Session, select
|
|
||||||
|
|
||||||
from eveal.schemas import Evepraisal, PriceReprocess
|
|
||||||
from eveal.database import engine, get_session
|
|
||||||
from eveal import models_sde, esi
|
|
||||||
|
|
||||||
SQLModel.metadata.create_all(engine) # use alembic?
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
async def root():
|
|
||||||
return {"message": "Hello World"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/reprocess/")
|
|
||||||
async def reprocess(ep_items: Evepraisal, ep_mat: Evepraisal, efficiency: float = .55, db: Session = Depends(get_session)) -> List[PriceReprocess]:
|
|
||||||
matprices = {item.typeID: {'sell': item.prices.sell.min, 'buy': item.prices.buy.max} for item in ep_mat.items}
|
|
||||||
|
|
||||||
item_reprocess: List[PriceReprocess] = []
|
|
||||||
for rawitem in ep_items.items:
|
|
||||||
item = db.get(models_sde.SDEType, rawitem.typeID)
|
|
||||||
buy_reprocess = sell_reprocess = 0.0
|
|
||||||
for mat in item.materials.all():
|
|
||||||
buy_reprocess += matprices[mat.material_type_id]['buy'] * mat.quantity/mat.type.portionSize * efficiency
|
|
||||||
sell_reprocess += matprices[mat.material_type_id]['sell'] * mat.quantity/mat.type.portionSize * efficiency
|
|
||||||
item_reprocess.append(PriceReprocess(typeID=rawitem.typeID,
|
|
||||||
buy=rawitem.prices.buy.max,
|
|
||||||
sell=rawitem.prices.sell.min,
|
|
||||||
buy_reprocess=buy_reprocess,
|
|
||||||
sell_reprocess=sell_reprocess,
|
|
||||||
name=rawitem.name
|
|
||||||
))
|
|
||||||
return item_reprocess
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/sde/types/{sde_type}/")
|
|
||||||
async def sde_types(sde_type: int | str, db: Session = Depends(get_session)) -> models_sde.SDEType:
|
|
||||||
try:
|
|
||||||
item = db.get(models_sde.SDEType, int(sde_type))
|
|
||||||
except ValueError:
|
|
||||||
item = db.query(models_sde.SDEType).filter(models_sde.SDEType.name == sde_type).one()
|
|
||||||
return item
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/sde/types/search")
|
|
||||||
async def sde_types_search(query: List[Dict[str, int | str | None | List]], db: Session = Depends(get_session)) -> List[models_sde.SDEType]:
|
|
||||||
items = []
|
|
||||||
for q in query:
|
|
||||||
qitems = db.query(models_sde.SDEType)
|
|
||||||
for k in q.keys():
|
|
||||||
value = q[k]
|
|
||||||
|
|
||||||
tokens = k.split("__")
|
|
||||||
key, mods = tokens[0], tokens[1:]
|
|
||||||
|
|
||||||
if "i" in mods:
|
|
||||||
condition = getattr(models_sde.SDEType, key).ilike(f"%{value}%") # change to icontains when sqlmodel start using sqlalchemy > 2.0
|
|
||||||
elif "in" in mods:
|
|
||||||
condition = getattr(models_sde.SDEType, key).in_(value)
|
|
||||||
else:
|
|
||||||
condition = getattr(models_sde.SDEType, key) == value
|
|
||||||
|
|
||||||
if "not" in mods:
|
|
||||||
condition = ~condition
|
|
||||||
|
|
||||||
qitems = qitems.filter(condition)
|
|
||||||
items.extend(qitems.all())
|
|
||||||
return items
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/esi/types/{sde_type}/market/{region_id}/")
|
|
||||||
async def sde_types_market(sde_type: int | str, region_id: int | str, db: Session = Depends(get_session)):
|
|
||||||
"""Get market orders for a type in a region. example: /esi/types/22291/market/10000002/"""
|
|
||||||
"""TODO: use ESIMarketOrder"""
|
|
||||||
return list(esi.esi_client.Market.get_markets_region_id_orders(order_type="all", type_id=sde_type, region_id=region_id))
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/_tools/get_all_mat")
|
|
||||||
async def _get_all_mat(db: Session = Depends(get_session)):
|
|
||||||
materials = db.query(models_sde.SDETypeMaterial).filter(models_sde.SDETypeMaterial.type.has(models_sde.SDEType.published == True)).all()
|
|
||||||
allmat = set()
|
|
||||||
for mat in materials:
|
|
||||||
allmat.add(mat.material_type.name)
|
|
||||||
return allmat
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import uvicorn
|
|
||||||
uvicorn.run("eveal.main:app", host="0.0.0.0", port=8000, reload=True)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Optional, List
|
|
||||||
from sqlmodel import SQLModel, Field, Relationship
|
|
||||||
|
|
||||||
|
|
||||||
class ESIMarketOrder(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
timestamp: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
|
||||||
|
|
||||||
region_id: int
|
|
||||||
type_id: int # TODO: link to SDE
|
|
||||||
location_id: int # TODO: link to SDE
|
|
||||||
volume_total: int
|
|
||||||
volume_remain: int
|
|
||||||
min_volume: int
|
|
||||||
order_id: int # TODO: use this as PK ? (will lose volume_remain history)
|
|
||||||
price: float
|
|
||||||
is_buy_order: bool
|
|
||||||
duration: int
|
|
||||||
issued: datetime
|
|
||||||
range: str # TODO: enum?
|
|
||||||
system_id: int # TODO: link to SDE
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
from typing import Optional, List
|
|
||||||
from sqlmodel import SQLModel, Field, Relationship
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
DynamicRelationship = partial(Relationship, sa_relationship_kwargs={"lazy": "dynamic"}) # change default lazy loading to dynamic
|
|
||||||
|
|
||||||
|
|
||||||
class SDEIcon(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
description: Optional[str] = None
|
|
||||||
iconFile: str
|
|
||||||
|
|
||||||
categories: List['SDECategory'] = DynamicRelationship(back_populates="icon")
|
|
||||||
groups: List['SDEGroup'] = DynamicRelationship(back_populates="icon")
|
|
||||||
marketgroups: List['SDEMarketGroup'] = DynamicRelationship(back_populates="icon")
|
|
||||||
types: List['SDEType'] = DynamicRelationship(back_populates="icon")
|
|
||||||
metagroups: List['SDEMetaGroup'] = DynamicRelationship(back_populates="icon")
|
|
||||||
|
|
||||||
|
|
||||||
class SDECategory(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
icon_id: Optional[int] = Field(default=None, foreign_key="sdeicon.id")
|
|
||||||
icon: Optional[SDEIcon] = Relationship(back_populates="categories")
|
|
||||||
|
|
||||||
name: str
|
|
||||||
published: bool
|
|
||||||
|
|
||||||
groups: List['SDEGroup'] = DynamicRelationship(back_populates="category")
|
|
||||||
|
|
||||||
|
|
||||||
class SDEGroup(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
anchorable: bool
|
|
||||||
anchored: bool
|
|
||||||
|
|
||||||
category_id: Optional[int] = Field(default=None, foreign_key="sdecategory.id")
|
|
||||||
category: Optional[SDECategory] = Relationship(back_populates="groups")
|
|
||||||
|
|
||||||
fittableNonSingletion: bool
|
|
||||||
|
|
||||||
icon_id: Optional[int] = Field(default=None, foreign_key="sdeicon.id")
|
|
||||||
icon: Optional[SDEIcon] = Relationship(back_populates="groups")
|
|
||||||
|
|
||||||
name: str
|
|
||||||
published: bool
|
|
||||||
useBasePrice: bool
|
|
||||||
|
|
||||||
types: List['SDEType'] = DynamicRelationship(back_populates="group")
|
|
||||||
|
|
||||||
|
|
||||||
class SDEMarketGroup(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
description: Optional[str] = None
|
|
||||||
hasTypes: bool
|
|
||||||
|
|
||||||
icon_id: Optional[int] = Field(default=None, foreign_key="sdeicon.id")
|
|
||||||
icon: Optional[SDEIcon] = Relationship(back_populates="marketgroups")
|
|
||||||
|
|
||||||
name: str
|
|
||||||
|
|
||||||
parent_marketgroup_id: Optional[int] = Field(default=None, foreign_key="sdemarketgroup.id")
|
|
||||||
parent_marketgroup: Optional['SDEMarketGroup'] = Relationship(back_populates="children_marketgroups",
|
|
||||||
sa_relationship_kwargs={"remote_side": 'SDEMarketGroup.id'}) # workaround for self reference: https://github.com/tiangolo/sqlmodel/issues/127#issuecomment-1224135123
|
|
||||||
children_marketgroups: List['SDEMarketGroup'] = DynamicRelationship(back_populates="parent_marketgroup")
|
|
||||||
|
|
||||||
types: List['SDEType'] = DynamicRelationship(back_populates="marketgroup")
|
|
||||||
|
|
||||||
|
|
||||||
class SDEMetaGroup(SQLModel, table=True):
|
|
||||||
id: int = Field(primary_key=True)
|
|
||||||
|
|
||||||
name: str
|
|
||||||
iconSuffix: Optional[str] = None
|
|
||||||
|
|
||||||
icon_id: Optional[int] = Field(default=None, foreign_key="sdeicon.id")
|
|
||||||
icon: Optional[SDEIcon] = Relationship(back_populates="metagroups")
|
|
||||||
|
|
||||||
types: List['SDEType'] = DynamicRelationship(back_populates="metagroup")
|
|
||||||
|
|
||||||
|
|
||||||
class SDEType(SQLModel, table=True):
|
|
||||||
id: int = Field(primary_key=True)
|
|
||||||
|
|
||||||
group_id: Optional[int] = Field(default=None, foreign_key="sdegroup.id")
|
|
||||||
group: Optional[SDEGroup] = Relationship(back_populates="types")
|
|
||||||
|
|
||||||
marketgroup_id: Optional[int] = Field(default=None, foreign_key="sdemarketgroup.id")
|
|
||||||
marketgroup: Optional[SDEMarketGroup] = Relationship(back_populates="types")
|
|
||||||
|
|
||||||
metagroup_id: Optional[int] = Field(default=None, foreign_key="sdemetagroup.id")
|
|
||||||
metagroup: Optional[SDEMetaGroup] = Relationship(back_populates="types")
|
|
||||||
|
|
||||||
name: str
|
|
||||||
published: bool = False
|
|
||||||
description: Optional[str] = None
|
|
||||||
basePrice: Optional[float] = None
|
|
||||||
|
|
||||||
icon_id: Optional[int] = Field(default=None, foreign_key="sdeicon.id")
|
|
||||||
icon: Optional[SDEIcon] = Relationship(back_populates="types")
|
|
||||||
|
|
||||||
volume: Optional[float] = None
|
|
||||||
portionSize: int
|
|
||||||
|
|
||||||
materials: List['SDETypeMaterial'] = DynamicRelationship(back_populates="type",
|
|
||||||
sa_relationship_kwargs={"lazy": "dynamic", "foreign_keys": '[SDETypeMaterial.type_id]'}) # https://github.com/tiangolo/sqlmodel/issues/10#issuecomment-1537445078
|
|
||||||
material_of: List['SDETypeMaterial'] = DynamicRelationship(back_populates="material_type",
|
|
||||||
sa_relationship_kwargs={"lazy": "dynamic", "foreign_keys": '[SDETypeMaterial.material_type_id]'}) # https://github.com/tiangolo/sqlmodel/issues/10#issuecomment-1537445078
|
|
||||||
|
|
||||||
|
|
||||||
class SDETypeMaterial(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
|
|
||||||
type_id: Optional[int] = Field(default=None, foreign_key="sdetype.id")
|
|
||||||
type: Optional[SDEType] = Relationship(back_populates="materials",
|
|
||||||
sa_relationship_kwargs={"primaryjoin": 'SDETypeMaterial.type_id==SDEType.id',
|
|
||||||
'lazy': 'joined'}) # workaround: https://github.com/tiangolo/sqlmodel/issues/10#issuecomment-1002835506
|
|
||||||
|
|
||||||
material_type_id: Optional[int] = Field(default=None, foreign_key="sdetype.id")
|
|
||||||
material_type: Optional[SDEType] = Relationship(back_populates="material_of",
|
|
||||||
sa_relationship_kwargs={"primaryjoin": 'SDETypeMaterial.material_type_id==SDEType.id',
|
|
||||||
'lazy': 'joined'}) # workaround: https://github.com/tiangolo/sqlmodel/issues/10#issuecomment-1002835506
|
|
||||||
|
|
||||||
quantity: int
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class Price(BaseModel):
|
|
||||||
avg: float
|
|
||||||
max: float
|
|
||||||
median: float
|
|
||||||
min: float
|
|
||||||
percentile: float
|
|
||||||
stddev: float
|
|
||||||
volume: int
|
|
||||||
order_count: int
|
|
||||||
|
|
||||||
class Prices(BaseModel):
|
|
||||||
all: Price
|
|
||||||
buy: Price
|
|
||||||
sell: Price
|
|
||||||
|
|
||||||
|
|
||||||
class Item(BaseModel):
|
|
||||||
name: str
|
|
||||||
typeID: int
|
|
||||||
typeName: str
|
|
||||||
typeVolume: float
|
|
||||||
quantity: int
|
|
||||||
prices: Prices
|
|
||||||
|
|
||||||
class Total_price(BaseModel):
|
|
||||||
buy: float
|
|
||||||
sell: float
|
|
||||||
volume: float
|
|
||||||
|
|
||||||
class Evepraisal(BaseModel):
|
|
||||||
id: str
|
|
||||||
created: int
|
|
||||||
kind: str
|
|
||||||
market_name: str
|
|
||||||
items: list[Item]
|
|
||||||
totals: Total_price
|
|
||||||
price_percentage: float
|
|
||||||
raw: str
|
|
||||||
|
|
||||||
|
|
||||||
class PriceReprocess(BaseModel):
|
|
||||||
typeID: int
|
|
||||||
buy: float
|
|
||||||
sell: float
|
|
||||||
buy_reprocess: float
|
|
||||||
sell_reprocess: float
|
|
||||||
name: str
|
|
||||||
|
|
||||||
|
|
||||||
173
import_sde.py
173
import_sde.py
@@ -1,173 +0,0 @@
|
|||||||
import yaml
|
|
||||||
from eveal.database import engine
|
|
||||||
from sqlmodel import Session, SQLModel, select
|
|
||||||
from eveal import models_sde
|
|
||||||
|
|
||||||
SQLModel.metadata.drop_all(engine) # use alembic!
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing SDE data...")
|
|
||||||
print("Importing icons...")
|
|
||||||
with open("static_eve/sde/fsd/iconIDs.yaml", "r", encoding="utf-8") as f:
|
|
||||||
icons = yaml.safe_load(f)
|
|
||||||
new_icons = total_icons = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, icon in icons.items():
|
|
||||||
db_icon = db.get(models_sde.SDEIcon, id)
|
|
||||||
if not db_icon:
|
|
||||||
db_icon = models_sde.SDEIcon(id=id)
|
|
||||||
db.add(db_icon)
|
|
||||||
new_icons += 1
|
|
||||||
db_icon.iconFile = icon['iconFile']
|
|
||||||
db_icon.description = icon['description'] if 'description' in icon else None
|
|
||||||
total_icons += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_icons} new icons / {total_icons} total icons.")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing categories...")
|
|
||||||
with open("static_eve/sde/fsd/categoryIDs.yaml", "r", encoding="utf-8") as f:
|
|
||||||
categories = yaml.safe_load(f)
|
|
||||||
new_categories = total_categories = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, category in categories.items():
|
|
||||||
# if category["published"] == False:
|
|
||||||
# continue
|
|
||||||
db_category = db.get(models_sde.SDECategory, id)
|
|
||||||
if not db_category:
|
|
||||||
db_category = models_sde.SDECategory(id=id)
|
|
||||||
db.add(db_category)
|
|
||||||
new_categories += 1
|
|
||||||
db_category.name = category['name']['en']
|
|
||||||
db_category.published = category['published']
|
|
||||||
db_category.icon_id = category['iconID'] if 'iconID' in category else None
|
|
||||||
total_categories += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_categories} new categories / {total_categories} total categories.")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing groups...")
|
|
||||||
with open("static_eve/sde/fsd/groupIDs.yaml", "r", encoding="utf-8") as f:
|
|
||||||
groups = yaml.safe_load(f)
|
|
||||||
new_groups = total_groups = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, group in groups.items():
|
|
||||||
# if group["published"] == False:
|
|
||||||
# continue
|
|
||||||
db_group = db.get(models_sde.SDEGroup, id)
|
|
||||||
if not db_group:
|
|
||||||
db_group = models_sde.SDEGroup(id=id)
|
|
||||||
db.add(db_group)
|
|
||||||
new_groups += 1
|
|
||||||
db_group.anchorable = group['anchorable']
|
|
||||||
db_group.anchored = group['anchored']
|
|
||||||
db_group.category_id = group['categoryID']
|
|
||||||
db_group.fittableNonSingletion = group['fittableNonSingleton']
|
|
||||||
db_group.icon_id = group['iconID'] if 'iconID' in group else None
|
|
||||||
db_group.name = group['name']['en']
|
|
||||||
db_group.published = group['published']
|
|
||||||
db_group.useBasePrice = group['useBasePrice']
|
|
||||||
total_groups += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_groups} new groups / {total_groups} total groups.")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing marketgroups...")
|
|
||||||
with open("static_eve/sde/fsd/marketGroups.yaml", "r", encoding="utf-8") as f:
|
|
||||||
marketgroups = yaml.safe_load(f)
|
|
||||||
new_marketgroups = total_marketgroups = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, marketgroup in marketgroups.items():
|
|
||||||
db_marketgroups = db.get(models_sde.SDEMarketGroup, id)
|
|
||||||
if not db_marketgroups:
|
|
||||||
db_marketgroups = models_sde.SDEMarketGroup(id=id)
|
|
||||||
db.add(db_marketgroups)
|
|
||||||
new_marketgroups += 1
|
|
||||||
db_marketgroups.description = marketgroup['descriptionID']['en'] if 'descriptionID' in marketgroup else None
|
|
||||||
db_marketgroups.hasTypes = marketgroup['hasTypes']
|
|
||||||
db_marketgroups.icon_id = marketgroup['iconID'] if 'iconID' in marketgroup else None
|
|
||||||
db_marketgroups.name = marketgroup['nameID']['en']
|
|
||||||
# db_marketgroups.parent_marketgroup_id = marketgroup['parentGroupID'] if 'parentGroupID' in marketgroup else None
|
|
||||||
total_marketgroups += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_marketgroups} marketgroups / {total_marketgroups} total marketgroups.")
|
|
||||||
print("Setting up marketgroup Parents...")
|
|
||||||
total_marketgroup_links = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, marketgroup in marketgroups.items():
|
|
||||||
db_marketgroups = db.get(models_sde.SDEMarketGroup, id)
|
|
||||||
db_marketgroups.parent_marketgroup_id = marketgroup['parentGroupID'] if 'parentGroupID' in marketgroup else None
|
|
||||||
total_marketgroup_links += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Updated {total_marketgroup_links} marketgroup Parents")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing metagroups...")
|
|
||||||
with open("static_eve/sde/fsd/metaGroups.yaml", "r", encoding="utf-8") as f:
|
|
||||||
metagroups = yaml.safe_load(f)
|
|
||||||
new_metagroups = total_metagroups = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, metagroup in metagroups.items():
|
|
||||||
db_metagroups = db.get(models_sde.SDEMetaGroup, id)
|
|
||||||
if not db_metagroups:
|
|
||||||
db_metagroups = models_sde.SDEMetaGroup(id=id)
|
|
||||||
db.add(db_metagroups)
|
|
||||||
new_metagroups += 1
|
|
||||||
db_metagroups.name = metagroup['nameID']['en']
|
|
||||||
db_metagroups.iconSuffix = metagroup['iconSuffix'] if 'iconSuffix' in metagroup else None
|
|
||||||
db_metagroups.icon_id = metagroup['iconID'] if 'iconID' in metagroup else None
|
|
||||||
total_metagroups += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_metagroups} metagroups / {total_metagroups} total metagroups.")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing types...")
|
|
||||||
with open("static_eve/sde/fsd/typeIDs.yaml", "r", encoding="utf-8") as f:
|
|
||||||
types = yaml.safe_load(f)
|
|
||||||
new_types = total_types = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, type in types.items():
|
|
||||||
# if type["published"] == False:
|
|
||||||
# continue
|
|
||||||
db_type = db.get(models_sde.SDEType, id)
|
|
||||||
if not db_type:
|
|
||||||
db_type = models_sde.SDEType(id=id)
|
|
||||||
db.add(db_type)
|
|
||||||
new_types += 1
|
|
||||||
db_type.group_id = type['groupID']
|
|
||||||
db_type.marketgroup_id = type['marketGroupID'] if 'marketGroupID' in type else None
|
|
||||||
db_type.name = type['name']['en']
|
|
||||||
db_type.published = type['published']
|
|
||||||
db_type.basePrice = type['basePrice'] if 'basePrice' in type else None
|
|
||||||
db_type.description = type['description']['en'] if 'description' in type else None
|
|
||||||
db_type.icon_id = type['iconID'] if 'iconID' in type else None
|
|
||||||
db_type.portionSize = type['portionSize']
|
|
||||||
db_type.volume = type['volume'] if 'volume' in type else None
|
|
||||||
db_type.metagroup_id = type['metaGroupID'] if 'metaGroupID' in type else None
|
|
||||||
total_types += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_types} types / {total_types} total types.")
|
|
||||||
|
|
||||||
|
|
||||||
print("Importing materials...")
|
|
||||||
with open("static_eve/sde/fsd/typeMaterials.yaml", "r", encoding="utf-8") as f:
|
|
||||||
materials = yaml.safe_load(f)
|
|
||||||
new_typemat = total_typemat = 0
|
|
||||||
with Session(engine) as db:
|
|
||||||
for id, material in materials.items():
|
|
||||||
for mat in material['materials']:
|
|
||||||
db_typemat = db.query(models_sde.SDETypeMaterial).filter(models_sde.SDETypeMaterial.type_id == id, models_sde.SDETypeMaterial.material_type_id == mat['materialTypeID']).one_or_none()
|
|
||||||
if not db_typemat:
|
|
||||||
db_typemat = models_sde.SDETypeMaterial()
|
|
||||||
db.add(db_typemat)
|
|
||||||
new_typemat += 1
|
|
||||||
db_typemat.type_id = id
|
|
||||||
db_typemat.material_type_id = mat['materialTypeID']
|
|
||||||
db_typemat.quantity = mat['quantity']
|
|
||||||
total_typemat += 1
|
|
||||||
db.commit()
|
|
||||||
print(f"Imported {new_typemat} materials / {total_typemat} total materials.")
|
|
||||||
|
|
||||||
|
|
||||||
print("DONE!")
|
|
||||||
22
manage.py
Executable file
22
manage.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'marbas.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
0
marbas/__init__.py
Normal file
0
marbas/__init__.py
Normal file
16
marbas/asgi.py
Normal file
16
marbas/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for marbas project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'marbas.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
8
marbas/pagination.py
Normal file
8
marbas/pagination.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import pagination
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPagination(pagination.PageNumberPagination):
|
||||||
|
page_size = 10
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
max_page_size = 250
|
||||||
|
|
||||||
157
marbas/settings.py
Normal file
157
marbas/settings.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""
|
||||||
|
Django settings for marbas project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 4.2.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = os.getenv("DRF_SECRET_KEY")
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = os.getenv("DRF_DEBUG", False) == "True"
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")
|
||||||
|
ALLOWED_HOSTS = [] if not any(ALLOWED_HOSTS) else ALLOWED_HOSTS
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'marbas.pagination.CustomPagination',
|
||||||
|
'DEFAULT_RENDERER_CLASSES': [
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
],
|
||||||
|
'DEFAULT_PARSER_CLASSES': [
|
||||||
|
'rest_framework.parsers.JSONParser',
|
||||||
|
],
|
||||||
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'api',
|
||||||
|
'sde',
|
||||||
|
'esi',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django_filters',
|
||||||
|
'rest_framework'
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'marbas.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': ["marbas/templates"],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'marbas.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
||||||
|
"LOCATION": f"redis://{os.getenv('REDIS_URL')}:{os.getenv('REDIS_PORT')}/{os.getenv('REDIS_DB')}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
"HOST": os.getenv("POSTGRES_HOST"),
|
||||||
|
"PORT": os.getenv("POSTGRES_PORT", 5432),
|
||||||
|
"USER": os.getenv("POSTGRES_USER"),
|
||||||
|
"PASSWORD": os.getenv("POSTGRES_PASSWORD"),
|
||||||
|
"NAME": os.getenv("POSTGRES_DB"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
ESI_SSO_CLIENT_ID = os.getenv("ESI_CLIENT_ID")
|
||||||
|
ESI_SSO_CLIENT_SECRET = os.getenv("ESI_SECRET_KEY")
|
||||||
|
ESI_SSO_CALLBACK_URL = os.getenv("ESI_CALLBACK_URL")
|
||||||
|
ESI_USER_AGENT = os.getenv("ESI_USER_AGENT")
|
||||||
|
ESI_USER_CONTACT_EMAIL = os.getenv("ESI_USER_AGENT")
|
||||||
21
marbas/templates/redoc.html
Normal file
21
marbas/templates/redoc.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ReDoc</title>
|
||||||
|
<!-- needed for adaptive design -->
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||||
|
<!-- ReDoc doesn't change outer page styles -->
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<redoc spec-url='{% url schema_url %}'></redoc>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
marbas/templates/swagger-ui.html
Normal file
28
marbas/templates/swagger-ui.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Swagger</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "{% url schema_url %}",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
layout: "BaseLayout",
|
||||||
|
requestInterceptor: (request) => {
|
||||||
|
request.headers['X-CSRFToken'] = "{{ csrf_token }}"
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
marbas/urls.py
Normal file
40
marbas/urls.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for marbas project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework.schemas import get_schema_view
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('api/', include("api.urls")),
|
||||||
|
path('sde/', include("sde.urls")),
|
||||||
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
path('sso/', include('esi.urls', namespace='esi')),
|
||||||
|
path('openapi/', get_schema_view(
|
||||||
|
title="marbas",
|
||||||
|
description="API for EvEal",
|
||||||
|
version="0.0.9"
|
||||||
|
), name='openapi-schema'),
|
||||||
|
path('swagger-ui/', TemplateView.as_view(
|
||||||
|
template_name='swagger-ui.html',
|
||||||
|
extra_context={'schema_url': 'openapi-schema'}
|
||||||
|
), name='swagger-ui'),
|
||||||
|
path('redoc/', TemplateView.as_view(
|
||||||
|
template_name='redoc.html',
|
||||||
|
extra_context={'schema_url': 'openapi-schema'}
|
||||||
|
), name='redoc'),
|
||||||
|
]
|
||||||
16
marbas/wsgi.py
Normal file
16
marbas/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for marbas project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'marbas.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
fastapi
|
django
|
||||||
httpx
|
djangorestframework
|
||||||
|
django-filter
|
||||||
|
markdown
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
sqlmodel
|
psycopg[binary]
|
||||||
esy
|
|
||||||
redis
|
redis
|
||||||
psycopg2-binary
|
pyyaml
|
||||||
|
uritemplate
|
||||||
|
django-esi
|
||||||
|
|||||||
0
sde/__init__.py
Normal file
0
sde/__init__.py
Normal file
6
sde/apps.py
Normal file
6
sde/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SdeConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'sde'
|
||||||
0
sde/management/__init__.py
Normal file
0
sde/management/__init__.py
Normal file
0
sde/management/commands/__init__.py
Normal file
0
sde/management/commands/__init__.py
Normal file
141
sde/management/commands/import_sde.py
Normal file
141
sde/management/commands/import_sde.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from sde.models import *
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Import SDE data from YAML files'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('path', type=str, help='Path to the SDE YAML files')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Import the SDE data
|
||||||
|
self.stdout.write(self.style.SUCCESS('Importing SDE data'))
|
||||||
|
self.import_sde_data(path=options['path'])
|
||||||
|
self.stdout.write(self.style.SUCCESS('Successfully imported SDE data'))
|
||||||
|
|
||||||
|
def import_sde_data(self, path):
|
||||||
|
self.import_icons(path + "/iconIDs.yaml")
|
||||||
|
self.import_categories(path + "/categoryIDs.yaml")
|
||||||
|
self.import_groups(path + "/groupIDs.yaml")
|
||||||
|
self.import_marketgroups(path + "/marketGroups.yaml")
|
||||||
|
self.import_metagroups(path + "/metaGroups.yaml")
|
||||||
|
self.import_types(path + "/typeIDs.yaml")
|
||||||
|
self.import_type_materials(path + "/typeMaterials.yaml")
|
||||||
|
|
||||||
|
def import_icons(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
icons = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, icon in icons.items():
|
||||||
|
SDEIcon.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'iconFile': icon['iconFile'],
|
||||||
|
'description': icon['description'] if "description" in icon else ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Icons imported'))
|
||||||
|
|
||||||
|
def import_categories(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
categories = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, category in categories.items():
|
||||||
|
SDECategory.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'icon': SDEIcon.objects.get(id=category['iconID']) if "iconID" in category else None,
|
||||||
|
'name': category['name']['en'],
|
||||||
|
'published': category['published']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Categories imported'))
|
||||||
|
|
||||||
|
def import_groups(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
groups = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, group in groups.items():
|
||||||
|
SDEGroup.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'category': SDECategory.objects.get(id=group['categoryID']),
|
||||||
|
'name': group['name'],
|
||||||
|
'published': group['published'],
|
||||||
|
'useBasePrice': group['useBasePrice'],
|
||||||
|
'fittableNonSingleton': group['fittableNonSingleton'],
|
||||||
|
'anchored': group['anchored'],
|
||||||
|
'anchorable': group['anchorable'],
|
||||||
|
'icon': SDEIcon.objects.get(id=group['iconID']) if "iconID" in group else None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Groups imported'))
|
||||||
|
|
||||||
|
def import_marketgroups(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
marketgroups = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, marketgroup in marketgroups.items():
|
||||||
|
SDEMarektGroup.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'icon': SDEIcon.objects.get(id=marketgroup['iconID']) if "iconID" in marketgroup else None,
|
||||||
|
'name': marketgroup['nameID']['en'],
|
||||||
|
'description': marketgroup['descriptionID']['en'] if "descriptionID" in marketgroup else "",
|
||||||
|
'hasTypes': marketgroup['hasTypes'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Marketgroups imported'))
|
||||||
|
for id, marketgroup in marketgroups.items():
|
||||||
|
if "parentGroupID" in marketgroup:
|
||||||
|
sde_mg = SDEMarektGroup.objects.get(id=id)
|
||||||
|
sde_mg.parent_marketgroup = SDEMarektGroup.objects.get(id=marketgroup['parentGroupID'])
|
||||||
|
sde_mg.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS('Marketgroups linked'))
|
||||||
|
|
||||||
|
def import_metagroups(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
metagroups = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, metagroup in metagroups.items():
|
||||||
|
SDEMetaGroup.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'icon': SDEIcon.objects.get(id=metagroup['iconID']) if "iconID" in metagroup else None,
|
||||||
|
'name': metagroup['nameID']['en'],
|
||||||
|
'iconSuffix': metagroup['iconSuffix'] if "iconSuffix" in metagroup else "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Metagroups imported'))
|
||||||
|
|
||||||
|
def import_types(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
types = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, type in types.items():
|
||||||
|
SDEType.objects.update_or_create(
|
||||||
|
id=id,
|
||||||
|
defaults={
|
||||||
|
'group': SDEGroup.objects.get(id=type['groupID']),
|
||||||
|
'marketgroup': SDEMarektGroup.objects.get(id=type['marketGroupID']) if "marketGroupID" in type else None,
|
||||||
|
'metagroup': SDEMetaGroup.objects.get(id=type['metaGroupID']) if "metaGroupID" in type else None,
|
||||||
|
'name': type['name']['en'],
|
||||||
|
'description': type['description']['en'] if "description" in type else "",
|
||||||
|
'published': type['published'],
|
||||||
|
'basePrice': type['basePrice'] if "basePrice" in type else 0,
|
||||||
|
'icon': SDEIcon.objects.get(id=type['iconID']) if "iconID" in type else None,
|
||||||
|
'volume': type['volume'] if "volume" in type else 0,
|
||||||
|
'portionSize': type['portionSize'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Types imported'))
|
||||||
|
|
||||||
|
def import_type_materials(self, path):
|
||||||
|
with open(path) as file:
|
||||||
|
type_materials = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
|
for id, type_material in type_materials.items():
|
||||||
|
for material in type_material['materials']:
|
||||||
|
SDETypeMaterial.objects.update_or_create(
|
||||||
|
type=SDEType.objects.get(id=id),
|
||||||
|
material_type=SDEType.objects.get(id=material['materialTypeID']),
|
||||||
|
defaults={
|
||||||
|
'quantity': material['quantity'],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Materials imported'))
|
||||||
108
sde/migrations/0001_initial.py
Normal file
108
sde/migrations/0001_initial.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# Generated by Django 4.2.6 on 2023-10-28 11:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDECategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField()),
|
||||||
|
('published', models.BooleanField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDEGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField()),
|
||||||
|
('published', models.BooleanField()),
|
||||||
|
('useBasePrice', models.BooleanField()),
|
||||||
|
('fittableNonSingleton', models.BooleanField()),
|
||||||
|
('anchored', models.BooleanField()),
|
||||||
|
('anchorable', models.BooleanField()),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='sde.sdecategory')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDEIcon',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('iconFile', models.CharField()),
|
||||||
|
('description', models.CharField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDEMarektGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField()),
|
||||||
|
('description', models.CharField(default='')),
|
||||||
|
('hasTypes', models.BooleanField()),
|
||||||
|
('icon', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='sde.sdeicon')),
|
||||||
|
('parent_marketgroup', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_marketgroups', to='sde.sdemarektgroup')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDEMetaGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField()),
|
||||||
|
('iconSuffix', models.CharField()),
|
||||||
|
('icon', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='metagroups', to='sde.sdeicon')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDEType',
|
||||||
|
fields=[
|
||||||
|
('id', models.IntegerField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField()),
|
||||||
|
('description', models.CharField()),
|
||||||
|
('published', models.BooleanField()),
|
||||||
|
('basePrice', models.FloatField()),
|
||||||
|
('volume', models.FloatField()),
|
||||||
|
('portionSize', models.IntegerField()),
|
||||||
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='types', to='sde.sdegroup')),
|
||||||
|
('icon', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='types', to='sde.sdeicon')),
|
||||||
|
('marketgroup', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='types', to='sde.sdemarektgroup')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SDETypeMaterial',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', models.IntegerField()),
|
||||||
|
('material_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='typematerials_of', to='sde.sdetype')),
|
||||||
|
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='typematerials', to='sde.sdetype')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sdetype',
|
||||||
|
name='materials',
|
||||||
|
field=models.ManyToManyField(related_name='material_of', through='sde.SDETypeMaterial', to='sde.sdetype'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sdetype',
|
||||||
|
name='metagroup',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='types', to='sde.sdemetagroup'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sdegroup',
|
||||||
|
name='icon',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='groups', to='sde.sdeicon'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sdecategory',
|
||||||
|
name='icon',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='categories', to='sde.sdeicon'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
sde/migrations/__init__.py
Normal file
0
sde/migrations/__init__.py
Normal file
67
sde/models.py
Normal file
67
sde/models.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class SDEIcon(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
iconFile = models.CharField()
|
||||||
|
description = models.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class SDECategory(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
icon = models.ForeignKey(SDEIcon, related_name="categories", null=True, on_delete=models.SET_NULL)
|
||||||
|
name = models.CharField()
|
||||||
|
published = models.BooleanField()
|
||||||
|
|
||||||
|
|
||||||
|
class SDEGroup(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
category = models.ForeignKey(SDECategory, related_name="groups", on_delete=models.CASCADE)
|
||||||
|
name = models.CharField()
|
||||||
|
published = models.BooleanField()
|
||||||
|
useBasePrice = models.BooleanField()
|
||||||
|
fittableNonSingleton = models.BooleanField()
|
||||||
|
anchored = models.BooleanField()
|
||||||
|
anchorable = models.BooleanField()
|
||||||
|
icon = models.ForeignKey(SDEIcon, related_name="groups", null=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMarektGroup(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
icon = models.ForeignKey(SDEIcon, null=True, on_delete=models.SET_NULL)
|
||||||
|
name = models.CharField()
|
||||||
|
description = models.CharField(default="")
|
||||||
|
hasTypes = models.BooleanField()
|
||||||
|
parent_marketgroup = models.ForeignKey("self", null=True, related_name="child_marketgroups", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMetaGroup(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
icon = models.ForeignKey(SDEIcon, related_name="metagroups", null=True, on_delete=models.SET_NULL)
|
||||||
|
name = models.CharField()
|
||||||
|
iconSuffix = models.CharField()
|
||||||
|
|
||||||
|
|
||||||
|
class SDEType(models.Model):
|
||||||
|
id = models.IntegerField(primary_key=True)
|
||||||
|
group = models.ForeignKey(SDEGroup, related_name="types", on_delete=models.CASCADE)
|
||||||
|
marketgroup = models.ForeignKey(SDEMarektGroup, related_name="types", on_delete=models.SET_NULL, null=True)
|
||||||
|
metagroup = models.ForeignKey(SDEMetaGroup, related_name="types", on_delete=models.SET_NULL, null=True)
|
||||||
|
name = models.CharField()
|
||||||
|
description = models.CharField()
|
||||||
|
published = models.BooleanField()
|
||||||
|
basePrice = models.FloatField()
|
||||||
|
icon = models.ForeignKey(SDEIcon, related_name="types", null=True, on_delete=models.SET_NULL)
|
||||||
|
volume = models.FloatField()
|
||||||
|
portionSize = models.IntegerField()
|
||||||
|
materials = models.ManyToManyField("self", through="SDETypeMaterial", symmetrical=False, related_name="material_of")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class SDETypeMaterial(models.Model):
|
||||||
|
type = models.ForeignKey(SDEType, on_delete=models.CASCADE, related_name="typematerials")
|
||||||
|
material_type = models.ForeignKey(SDEType, on_delete=models.CASCADE, related_name="typematerials_of")
|
||||||
|
|
||||||
|
quantity = models.IntegerField()
|
||||||
54
sde/serializers.py
Normal file
54
sde/serializers.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from sde.models import *
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class SDEIconSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDEIcon
|
||||||
|
# fields = ['id', 'iconFile', 'description']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SDECategorySerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDECategory
|
||||||
|
# fields = ['id', 'icon', 'name', 'published']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SDEGroupSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDEGroup
|
||||||
|
# fields = ['id', 'category', 'name', 'published', 'useBasePrice', 'fittableNonSingleton', 'anchored', 'anchorable', 'icon']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMarektGroupSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDEMarektGroup
|
||||||
|
# fields = ['id', 'icon', 'name', 'description', 'hasTypes', 'parent_marketgroup']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMetaGroupSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDEMetaGroup
|
||||||
|
# fields = ['id', 'icon', 'name', 'iconSuffix']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class SDETypeMaterialSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SDETypeMaterial
|
||||||
|
# fields = ['type', 'material', 'quantity']
|
||||||
|
# fields = "__all__"
|
||||||
|
exclude = ['id', 'type']
|
||||||
|
|
||||||
|
|
||||||
|
class SDETypeSerializer(serializers.ModelSerializer):
|
||||||
|
# typematerials = SDETypeMaterialSerializer(many=True, read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = SDEType
|
||||||
|
# fields = ['id', 'group', 'marketgroup', 'metagroup', 'name', 'description', 'published', 'basePrice', 'icon', 'volume', 'portionSize', 'materials']
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
16
sde/urls.py
Normal file
16
sde/urls.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from . import views
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'icons', views.SDEIconViewSet)
|
||||||
|
router.register(r'categories', views.SDECategoryViewSet)
|
||||||
|
router.register(r'groups', views.SDEGroupViewSet)
|
||||||
|
router.register(r'marketgroups', views.SDEMarketGroupViewSet)
|
||||||
|
router.register(r'metagroups', views.SDEMetaGroupViewSet)
|
||||||
|
router.register(r'types', views.SDETypeViewSet)
|
||||||
|
router.register(r'typematerials', views.SDETypeMaterialViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
46
sde/views.py
Normal file
46
sde/views.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from sde.serializers import *
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
|
||||||
|
class SDEIconViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDEIcon.objects.all()
|
||||||
|
serializer_class = SDEIconSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDECategoryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDECategory.objects.all()
|
||||||
|
serializer_class = SDECategorySerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDEGroupViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDEGroup.objects.all()
|
||||||
|
serializer_class = SDEGroupSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMarketGroupViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDEMarektGroup.objects.all()
|
||||||
|
serializer_class = SDEMarektGroupSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDEMetaGroupViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDEMetaGroup.objects.all()
|
||||||
|
serializer_class = SDEMetaGroupSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDETypeViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDEType.objects.all()
|
||||||
|
serializer_class = SDETypeSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
|
class SDETypeMaterialViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = SDETypeMaterial.objects.all()
|
||||||
|
serializer_class = SDETypeMaterialSerializer
|
||||||
|
# permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user