rework-drf #4

Merged
Kaladaran merged 13 commits from rework-drf into main 2023-10-29 22:08:44 +01:00
41 changed files with 984 additions and 538 deletions

View File

@@ -5,6 +5,7 @@ ESI_USER_AGENT=
REDIS_URL=
REDIS_PORT=
REDIS_DB=
REDIS_PASSWORD=
SQLITE_DB_PATH=
@@ -12,3 +13,7 @@ POSTGRES_HOST=
POSTGRES_PASSWORD=
POSTGRES_USER=
POSTGRES_DB=
DRF_SECRET_KEY=
DRF_DEBUG=
ALLOWED_HOSTS=

View File

@@ -6,9 +6,12 @@ ENV PYTHONDONTWRITEBYTECODE=1
RUN adduser -u 5678 --disabled-password --gecos "" appuser
WORKDIR /app
COPY --chown=appuser:appuser manage.py /app/manage.py
COPY 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
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
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
api/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

View 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)),
],
),
]

View File

15
api/models.py Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

14
api/urls.py Normal file
View 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
View 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)

View File

@@ -1,22 +1,44 @@
version: '3'
services:
eveal:
image: mabras:local
migrations:
image: marbas:local
build:
context: .
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
ports:
- 8000:8000
user: "1000:1000"
volumes:
- ./eveal:/app/eveal
command: ["uvicorn", "eveal.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
- ./marbas:/app/marbas
- ./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:
redis:
condition: service_healthy
db:
condition: service_healthy
migrations:
condition: service_completed_successfully
# elasticsearch:
# condition: service_healthy
@@ -33,14 +55,14 @@ services:
ports:
- 5432:5432
volumes:
- mabras_dbdata:/var/lib/postgresql/data
- marbas_dbdata:/var/lib/postgresql/data
env_file:
- .env
healthcheck:
test: pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}
interval: 10s
interval: 1s
timeout: 3s
retries: 3
retries: 10
# elasticsearch:
# image: elasticsearch:latest
@@ -58,4 +80,4 @@ services:
# retries: 3
volumes:
mabras_dbdata:
marbas_dbdata:

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View File

16
marbas/asgi.py Normal file
View 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
View 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
View 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")

View 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>

View 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
View 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
View 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()

View File

@@ -1,7 +1,10 @@
fastapi
httpx
django
djangorestframework
django-filter
markdown
uvicorn[standard]
sqlmodel
esy
psycopg[binary]
redis
psycopg2-binary
pyyaml
uritemplate
django-esi

0
sde/__init__.py Normal file
View File

6
sde/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SdeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'sde'

View File

View File

View 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'))

View 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'),
),
]

View File

67
sde/models.py Normal file
View 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
View 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
View 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
View 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]