Skip to content

Commit

Permalink
feat(api): adding admin to initiate batch model score requests (#653)
Browse files Browse the repository at this point in the history
* feat(api): adding admin to initiate batch model score requests

* cleanup
  • Loading branch information
lucianHymer authored Aug 5, 2024
1 parent 642d65f commit 830ca20
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 1 deletion.
96 changes: 95 additions & 1 deletion api/registry/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
from datetime import UTC, datetime

import boto3
from asgiref.sync import async_to_sync
from django import forms
from django.contrib import admin, messages
from django.shortcuts import redirect, render
from django.urls import path

from registry.api.schema import SubmitPassportPayload
from registry.api.v1 import ahandle_submit_passport
from registry.models import (
BatchModelScoringRequest,
BatchRequestStatus,
Event,
GTCStakeEvent,
HashScorerLink,
Expand All @@ -11,6 +20,22 @@
Stamp,
)
from scorer.scorer_admin import ScorerModelAdmin
from scorer.settings import (
BULK_MODEL_SCORE_REQUESTS_RESULTS_FOLDER,
BULK_SCORE_REQUESTS_ADDRESS_LIST_FOLDER,
BULK_SCORE_REQUESTS_BUCKET_NAME,
)

ONE_HOUR = 60 * 60

_s3_client = None


def get_s3_client():
global _s3_client
if not _s3_client:
_s3_client = boto3.client("s3")
return _s3_client


@admin.action(
Expand All @@ -37,7 +62,7 @@ def recalculate_user_score(modeladmin, request, queryset):

modeladmin.message_user(
request,
f"Have succesfully rescored: {rescored_ids}",
f"Have successfully rescored: {rescored_ids}",
level=messages.SUCCESS,
)
if failed_rescoring:
Expand Down Expand Up @@ -148,3 +173,72 @@ class GTCStakeEventAdmin(ScorerModelAdmin):
"staker",
"event_type",
]


class BatchModelScoringRequestCsvImportForm(forms.Form):
models = forms.CharField(
max_length=100,
required=True,
help_text='Example: "nft,ethereum_activity"',
)
address_list = forms.FileField(required=True)


@admin.register(BatchModelScoringRequest)
class BatchModelScoringRequestAdmin(ScorerModelAdmin):
change_list_template = "registry/batch_model_scoring_request_changelist.html"
list_display = ["id", "created_at"]
readonly_fields = [
field.name for field in BatchModelScoringRequest._meta.get_fields()
] + ["address_list", "results"]

def address_list(self, obj):
object_name = f"{BULK_SCORE_REQUESTS_ADDRESS_LIST_FOLDER}/{obj.s3_filename}"
return get_s3_client().generate_presigned_url(
"get_object",
Params={"Bucket": BULK_SCORE_REQUESTS_BUCKET_NAME, "Key": object_name},
ExpiresIn=ONE_HOUR,
)

def results(self, obj):
if obj.status != BatchRequestStatus.DONE:
return None

object_name = f"{BULK_MODEL_SCORE_REQUESTS_RESULTS_FOLDER}/{obj.s3_filename}"
return get_s3_client().generate_presigned_url(
"get_object",
Params={"Bucket": BULK_SCORE_REQUESTS_BUCKET_NAME, "Key": object_name},
ExpiresIn=ONE_HOUR,
)

def get_urls(self):
return [
path("import-csv/", self.import_csv),
] + super().get_urls()

def import_csv(self, request):
if request.method == "POST":
filename = datetime.now(UTC).strftime("%Y-%m-%d_%H-%M-%S") + ".csv"
object_name = f"{BULK_SCORE_REQUESTS_ADDRESS_LIST_FOLDER}/{filename}"

get_s3_client().upload_fileobj(
request.FILES["address_list"],
BULK_SCORE_REQUESTS_BUCKET_NAME,
object_name,
)

obj = BatchModelScoringRequest.objects.create(
model_list=request.POST.get("models"),
s3_filename=filename,
status=BatchRequestStatus.PENDING,
)

return redirect(f"../{obj.pk}/change/")

form = BatchModelScoringRequestCsvImportForm()
payload = {"form": form}
return render(
request,
"registry/batch_model_scoring_request_csv_import_form.html",
payload,
)
42 changes: 42 additions & 0 deletions api/registry/migrations/0034_batchmodelscoringrequest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 4.2.6 on 2024-08-02 22:24

from django.db import migrations, models
import registry.models


class Migration(migrations.Migration):

dependencies = [
("registry", "0033_score_expiration_date"),
]

operations = [
migrations.CreateModel(
name="BatchModelScoringRequest",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("s3_filename", models.CharField(max_length=100)),
("model_list", models.CharField(max_length=100)),
(
"status",
models.CharField(
choices=[
("PENDING", registry.models.BatchRequestStatus["PENDING"]),
("DONE", registry.models.BatchRequestStatus["DONE"]),
],
default=registry.models.BatchRequestStatus["PENDING"],
max_length=10,
),
),
],
),
]
20 changes: 20 additions & 0 deletions api/registry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from enum import Enum


class Passport(models.Model):
Expand Down Expand Up @@ -194,3 +195,22 @@ class Meta:
name="gtc_staking_index_by_staker",
),
]


class BatchRequestStatus(str, Enum):
PENDING = "PENDING"
DONE = "DONE"

def __str__(self):
return f"{self.value}"


class BatchModelScoringRequest(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
s3_filename = models.CharField(max_length=100, null=False, blank=False)
model_list = models.CharField(max_length=100, null=False, blank=False)
status = models.CharField(
max_length=10,
choices=[(status.value, status) for status in BatchRequestStatus],
default=BatchRequestStatus.PENDING,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends 'admin/change_list.html' %}

{% block object-tools %}
<a style="padding: 8px; margin: 20px; font-weight: bold; text-decoration: none; border-radius: 12px; background-color: blue; color: white;"
href="import-csv/">
Upload Addresses 📁
</a>
<br />
<br />
{{ block.super }}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends 'admin/base.html' %}

{% block content %}
<div>
<form action="." method="POST" enctype="multipart/form-data">
{{ form.as_p }}
{% csrf_token %}

<button type="submit">Submit</button>
</form>
</div>
<br />

{% endblock %}
10 changes: 10 additions & 0 deletions api/scorer/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,4 +454,14 @@
# ...
]

BULK_SCORE_REQUESTS_BUCKET_NAME = env(
"BULK_SCORE_REQUESTS_BUCKET_NAME", default="bulk-score-requests"
)
BULK_SCORE_REQUESTS_ADDRESS_LIST_FOLDER = env(
"BULK_SCORE_REQUESTS_ADDRESS_LIST_FOLDER", default="address-lists"
)
BULK_MODEL_SCORE_REQUESTS_RESULTS_FOLDER = env(
"BULK_MODEL_SCORE_REQUESTS_RESULTS_FOLDER", default="model-score-results"
)

VERIFIER_URL = env("VERIFIER_URL", default="http://localhost:8001/verifier/verify")

0 comments on commit 830ca20

Please sign in to comment.