diff --git a/.env.template b/.env.template index c5f00e2..17626bb 100644 --- a/.env.template +++ b/.env.template @@ -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= diff --git a/Dockerfile b/Dockerfile index ee2f1c1..17620d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file +CMD ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/eveal/__init__.py b/api/__init__.py similarity index 100% rename from eveal/__init__.py rename to api/__init__.py diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..976b1fb --- /dev/null +++ b/api/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..3041322 --- /dev/null +++ b/api/models.py @@ -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) diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..beebeae --- /dev/null +++ b/api/serializers.py @@ -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__' diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..0ad437f --- /dev/null +++ b/api/urls.py @@ -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'), +] diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..7291476 --- /dev/null +++ b/api/views.py @@ -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) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2e1512c..2102756 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -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: diff --git a/eveal/database.py b/eveal/database.py deleted file mode 100644 index de94950..0000000 --- a/eveal/database.py +++ /dev/null @@ -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() diff --git a/eveal/esi.py b/eveal/esi.py deleted file mode 100644 index d186d94..0000000 --- a/eveal/esi.py +++ /dev/null @@ -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() diff --git a/eveal/main.py b/eveal/main.py deleted file mode 100644 index 26214a7..0000000 --- a/eveal/main.py +++ /dev/null @@ -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) diff --git a/eveal/models_esi.py b/eveal/models_esi.py deleted file mode 100644 index 0b26ed6..0000000 --- a/eveal/models_esi.py +++ /dev/null @@ -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 - - diff --git a/eveal/models_sde.py b/eveal/models_sde.py deleted file mode 100644 index 2d6e0be..0000000 --- a/eveal/models_sde.py +++ /dev/null @@ -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 diff --git a/eveal/schemas.py b/eveal/schemas.py deleted file mode 100644 index 5e0d287..0000000 --- a/eveal/schemas.py +++ /dev/null @@ -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 - - diff --git a/import_sde.py b/import_sde.py deleted file mode 100644 index bdb8197..0000000 --- a/import_sde.py +++ /dev/null @@ -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!") \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..54b8e05 --- /dev/null +++ b/manage.py @@ -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() diff --git a/marbas/__init__.py b/marbas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/marbas/asgi.py b/marbas/asgi.py new file mode 100644 index 0000000..82e99c4 --- /dev/null +++ b/marbas/asgi.py @@ -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() diff --git a/marbas/pagination.py b/marbas/pagination.py new file mode 100644 index 0000000..c4d4f9b --- /dev/null +++ b/marbas/pagination.py @@ -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 + diff --git a/marbas/settings.py b/marbas/settings.py new file mode 100644 index 0000000..b5a328f --- /dev/null +++ b/marbas/settings.py @@ -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") diff --git a/marbas/templates/redoc.html b/marbas/templates/redoc.html new file mode 100644 index 0000000..c02bbf9 --- /dev/null +++ b/marbas/templates/redoc.html @@ -0,0 +1,21 @@ + + + + ReDoc + + + + + + + + + + + + \ No newline at end of file diff --git a/marbas/templates/swagger-ui.html b/marbas/templates/swagger-ui.html new file mode 100644 index 0000000..2977649 --- /dev/null +++ b/marbas/templates/swagger-ui.html @@ -0,0 +1,28 @@ + + + + Swagger + + + + + +
+ + + + \ No newline at end of file diff --git a/marbas/urls.py b/marbas/urls.py new file mode 100644 index 0000000..0c03814 --- /dev/null +++ b/marbas/urls.py @@ -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'), +] diff --git a/marbas/wsgi.py b/marbas/wsgi.py new file mode 100644 index 0000000..aab9192 --- /dev/null +++ b/marbas/wsgi.py @@ -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() diff --git a/requirements.txt b/requirements.txt index 8c0c8cc..b304495 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,10 @@ -fastapi -httpx +django +djangorestframework +django-filter +markdown uvicorn[standard] -sqlmodel -esy +psycopg[binary] redis -psycopg2-binary +pyyaml +uritemplate +django-esi diff --git a/sde/__init__.py b/sde/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sde/apps.py b/sde/apps.py new file mode 100644 index 0000000..7ab8d8d --- /dev/null +++ b/sde/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SdeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'sde' diff --git a/sde/management/__init__.py b/sde/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sde/management/commands/__init__.py b/sde/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sde/management/commands/import_sde.py b/sde/management/commands/import_sde.py new file mode 100644 index 0000000..2025b7e --- /dev/null +++ b/sde/management/commands/import_sde.py @@ -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')) diff --git a/sde/migrations/0001_initial.py b/sde/migrations/0001_initial.py new file mode 100644 index 0000000..a76a837 --- /dev/null +++ b/sde/migrations/0001_initial.py @@ -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'), + ), + ] diff --git a/sde/migrations/__init__.py b/sde/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sde/models.py b/sde/models.py new file mode 100644 index 0000000..0d0ef09 --- /dev/null +++ b/sde/models.py @@ -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() diff --git a/sde/serializers.py b/sde/serializers.py new file mode 100644 index 0000000..7f57f3f --- /dev/null +++ b/sde/serializers.py @@ -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__" + diff --git a/sde/urls.py b/sde/urls.py new file mode 100644 index 0000000..152956e --- /dev/null +++ b/sde/urls.py @@ -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)), +] diff --git a/sde/views.py b/sde/views.py new file mode 100644 index 0000000..e831353 --- /dev/null +++ b/sde/views.py @@ -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] + +