Initial commit

master
Anulax ago%!(EXTRA string=1 month)
commit 8f8d6db35d
  1. 5
      .gitignore
  2. 17
      ai.py
  3. 0
      api/__init__.py
  4. 58
      api/admin.py
  5. 6
      api/apps.py
  6. 0
      api/management/__init__.py
  7. 0
      api/management/commands/__init__.py
  8. 21
      api/management/commands/gen_master_key.py
  9. 27
      api/migrations/0001_initial.py
  10. 20
      api/migrations/0002_aimodel.py
  11. 34
      api/migrations/0003_conversation_message.py
  12. 17
      api/migrations/0004_remove_conversation_model.py
  13. 26
      api/migrations/0005_alter_conversation_user_alter_message_conversation.py
  14. 17
      api/migrations/0006_remove_conversation_user.py
  15. 22
      api/migrations/0007_conversation_user.py
  16. 0
      api/migrations/__init__.py
  17. 47
      api/models.py
  18. 13
      api/serializers.py
  19. 14
      api/templates/drf-yasg/redoc.html
  20. 3
      api/tests.py
  21. 34
      api/urls.py
  22. 185
      api/views.py
  23. 0
      botzilla/__init__.py
  24. 16
      botzilla/asgi.py
  25. 170
      botzilla/settings.py
  26. 45
      botzilla/urls.py
  27. 16
      botzilla/wsgi.py
  28. 22
      manage.py
  29. 25
      requirements.txt
  30. 4
      static/drf-yasg/style.css
  31. BIN
      static/favicon.ico
  32. 3
      static/logo.svg

5
.gitignore vendored

@ -0,0 +1,5 @@
__pycache__
.venv
**.sqlite3
.env
token

17
ai.py

@ -0,0 +1,17 @@
import ollama
# Call chat with stream=True to enable streaming
stream = ollama.chat(
model='llama3:8b',
messages=[
{
'role': 'user',
'content': 'Hello, could you make a presentation of your self',
}
],
stream=True
)
# Process the streamed response chunks
for chunk in stream:
print(chunk['message']['content'], end='', flush=True)

@ -0,0 +1,58 @@
from django.contrib import admin
# Register your models here.
from django.contrib import admin
from .models import MasterKey
from django.contrib.auth import get_user_model
User = get_user_model()
@admin.register(MasterKey)
class MasterKeyAdmin(admin.ModelAdmin):
list_display = ('key_id', 'description', 'created_at', 'last_used', 'is_active')
list_filter = ('is_active', 'created_at')
search_fields = ('description', 'key_id')
readonly_fields = ('key_id', 'key_value', 'created_at', 'last_used')
fieldsets = (
(None, {
'fields': ('key_id', 'description', 'is_active')
}),
('Permissions', {
'fields': ('permissions',)
}),
('Security Info', {
'fields': ('created_at', 'last_used'),
'classes': ('collapse',)
}),
)
actions = ['deactivate_keys', 'activate_keys']
def deactivate_keys(self, request, queryset):
queryset.update(is_active=False)
deactivate_keys.short_description = "Deactivate selected keys"
def activate_keys(self, request, queryset):
queryset.update(is_active=True)
activate_keys.short_description = "Activate selected keys"
def has_change_permission(self, request, obj=None):
# Prevent editing of key_value for security
if obj is not None:
return True
return super().has_change_permission(request, obj)
# @admin.register(User)
# class UserAdmin(admin.ModelAdmin):
# list_display = ('last_login', 'username', 'is_staff', 'date_joined', 'is_active')
# list_filter = ('is_active', 'date_joined', 'is_staff')
# search_fields = ('username', 'date_joined')
# readonly_fields = ('username', 'date_joined', 'last_login')
# fieldsets = (
# (None, {
# 'fields': ('username', 'date_joined')
# }),
# ('Security Info', {
# 'fields': ('is_staff', 'last_login', 'is_active'),
# 'classes': ('collapse',)
# }),
# )

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

@ -0,0 +1,21 @@
from django.core.management.base import BaseCommand
from api.models import MasterKey
class Command(BaseCommand):
help = 'Generate a new master key'
def add_arguments(self, parser):
parser.add_argument('--description', type=str, help='Description for the key')
parser.add_argument('--permissions', nargs='+', help='List of permissions for the key')
def handle(self, *args, **kwargs):
description = kwargs.get('description') or 'Generated via management command'
permissions = kwargs.get('permissions') or []
master_key = MasterKey.generate_key(
description=description,
permissions=permissions
)
self.stdout.write(self.style.SUCCESS(f'Successfully created master key: {master_key.key_id}'))
self.stdout.write(self.style.WARNING(f'Key value (save this securely, it will not be shown again): {master_key.key_value}'))

@ -0,0 +1,27 @@
# Generated by Django 5.2.1 on 2025-05-11 16:40
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='MasterKey',
fields=[
('key_id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('key_value', models.CharField(max_length=255, unique=True)),
('description', models.CharField(blank=True, max_length=255)),
('created_at', models.DateTimeField(auto_now_add=True)),
('last_used', models.DateTimeField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
('permissions', models.JSONField(default=list)),
],
),
]

@ -0,0 +1,20 @@
# Generated by Django 5.2.1 on 2025-05-11 17:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AiModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
],
),
]

@ -0,0 +1,34 @@
# Generated by Django 5.2.1 on 2025-05-11 17:46
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0002_aimodel'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Conversation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.aimodel')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Message',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('role', models.CharField(max_length=255)),
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.conversation')),
],
),
]

@ -0,0 +1,17 @@
# Generated by Django 5.2.1 on 2025-05-12 11:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('api', '0003_conversation_message'),
]
operations = [
migrations.RemoveField(
model_name='conversation',
name='model',
),
]

@ -0,0 +1,26 @@
# Generated by Django 5.2.1 on 2025-05-12 12:49
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0004_remove_conversation_model'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='conversation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='message',
name='conversation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='api.conversation'),
),
]

@ -0,0 +1,17 @@
# Generated by Django 5.2.1 on 2025-05-12 16:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('api', '0005_alter_conversation_user_alter_message_conversation'),
]
operations = [
migrations.RemoveField(
model_name='conversation',
name='user',
),
]

@ -0,0 +1,22 @@
# Generated by Django 5.2.1 on 2025-05-12 16:53
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0006_remove_conversation_user'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='conversation',
name='user',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]

@ -0,0 +1,47 @@
from django.db import models
from django.contrib.auth import get_user_model
import uuid
import secrets
User = get_user_model()
class MasterKey(models.Model):
key_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
key_value = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=255, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
last_used = models.DateTimeField(null=True, blank=True)
is_active = models.BooleanField(default=True)
permissions = models.JSONField(default=list)
def __str__(self):
return f"Master Key: {self.key_id}"
@classmethod
def generate_key(cls, description="", permissions=None):
"""Generate a new secure master key"""
if permissions is None:
permissions = []
# Generate a secure random token
key_value = secrets.token_urlsafe(64)
return cls.objects.create(
key_value=key_value,
description=description,
permissions=permissions
)
class AiModel(models.Model):
name = models.CharField(max_length=255)
class Conversation(models.Model):
title = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="conversations")
class Message(models.Model):
content = models.TextField()
role = models.CharField(max_length=255)
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name="messages")

@ -0,0 +1,13 @@
from rest_framework import serializers
from .models import Conversation, Message
class ConversationSerializer(serializers.ModelSerializer):
class Meta:
model = Conversation
fields = ['id', 'title']
class MessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = ['id', 'content', 'role']

@ -0,0 +1,14 @@
{% extends "drf-yasg/redoc.html" %}
{% block favicon %}
<link rel="icon" type="image/ico" href="/static/favicon.ico"/>
<style>
img[alt="Botzilla logo"] {
max-height: 80px !important;
width: auto;
padding-top: 3px !important;
padding-bottom: 3px !important;
}
</style>
{% endblock %}

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

@ -0,0 +1,34 @@
"""
URL configuration for botzilla project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.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 path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
from .views import MasterKeyTokenObtainView
from .views import ConversationView
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'conversations', ConversationView)
urlpatterns = [
path('token/', MasterKeyTokenObtainView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('', include(router.urls)),
]

@ -0,0 +1,185 @@
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.tokens import RefreshToken
from django.views.decorators.http import require_http_methods
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAuthenticated
from django.utils import timezone
from django.contrib.auth import get_user_model
from .models import MasterKey
from .models import Conversation
from .models import Message
from .serializers import ConversationSerializer, MessageSerializer
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
import ollama
User = get_user_model()
class ConversationView(viewsets.ModelViewSet):
queryset = Conversation.objects.all()
serializer_class = ConversationSerializer
permission_classes = [IsAuthenticated] # JWT token auth required
def get_queryset(self):
queryset = Conversation.objects.all()
return queryset
def perform_create(self, serializer):
"""Associate new product with current user"""
serializer.save(user=self.request.user)
@swagger_auto_schema(
method='get',
operation_description="Get messages of the conversation",
responses={
200: openapi.Response('List of messages', MessageSerializer(many=True)),
400: 'Bad Request',
404: 'Conversation not found'
},
manual_parameters=[
openapi.Parameter(
'category',
openapi.IN_QUERY,
description="Filter featured items by category",
type=openapi.TYPE_STRING
)
]
)
@action(detail=True, methods=['get'])
def contents(self, request, pk=None):
conversation = self.get_object()
messages = conversation.messages.all()
return Response(data=list(messages.values()))
@swagger_auto_schema(
method='post',
operation_description="Discutes with the ai",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'content': openapi.Schema(type=openapi.TYPE_STRING, description='Contents of the message'),
},
required=['content']
),
responses={
200: openapi.Response('Message processed successfully', MessageSerializer),
400: 'Bad Request',
}
)
@action(detail=True, methods=['post'])
def prompt(self, request, pk=None):
conversation = self.get_object()
messages = [{
"role": "system",
"content": """
You must strictly refuse to engage with ANY of the following:
1. Violence or harm (even fictional or hypothetical scenarios)
2. ANY explicit, suggestive, or romantic content
3. Controversial political topics
4. ANY content that could potentially be misused
5. Medical, legal, or financial advice
6. Personal information or privacy violations
7. Anything that could be remotely offensive to anyone
If you detect such content, immediately respond with:
"I cannot assist with that request as it appears to be inappropriate. I'm designed to be helpful, but within strict ethical boundaries. Is there something else I can help you with?"
"""
}]
for message in conversation.messages.all():
if message:
messages.append({
"role": message.role,
"content": message.content
})
messages.append({
"role": "user",
"content": request.data.get("content", "")
})
res = ollama.chat(model="gemma3", messages=messages)
Message(role="user", content=request.data.get("content", ""), conversation=conversation).save()
Message(role="assistant", content=res['message']['content'], conversation=conversation).save()
return Response(data={
"content": res['message']['content']
})
class MasterKeyTokenObtainView(TokenObtainPairView):
permission_classes = [AllowAny]
@swagger_auto_schema(
operation_description="Creates a token for a user (creates the user if doesn't exists)",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'username': openapi.Schema(type=openapi.TYPE_STRING, description='Identifier of the user'),
'master_key': openapi.Schema(type=openapi.TYPE_STRING, description='Key of the authorizied application'),
},
required=['username', 'master_key']
),
responses={
200: openapi.Response('API tokens of the user', openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'access': openapi.Schema(type=openapi.TYPE_STRING, description='API access token of the user'),
'refresh': openapi.Schema(type=openapi.TYPE_STRING, description='API refresh token of the user'),
},
required=['access', 'access']
)),
400: 'Bad Request',
401: 'Bad master key'
}
)
def post(self, request, *args, **kwargs):
# Get the provided master key from the request
master_key_value = request.data.get('master_key')
if not master_key_value:
return Response(
{'error': 'Master key is required'},
status=status.HTTP_400_BAD_REQUEST
)
# Verify the master key locally
try:
master_key = MasterKey.objects.get(key_value=master_key_value, is_active=True)
except MasterKey.DoesNotExist:
return Response(
{'error': 'Invalid or inactive master key'},
status=status.HTTP_401_UNAUTHORIZED
)
# Update last used timestamp
master_key.last_used = timezone.now()
master_key.save(update_fields=['last_used'])
# Get user identifier from request or use a default
user_identifier = request.data.get('username', f'service_user_{master_key.key_id}')
# Get or create a user associated with this master key
user, created = User.objects.get_or_create(
username=user_identifier,
defaults={
'is_active': True
}
)
# Generate tokens manually
refresh = RefreshToken.for_user(user)
# Add custom claims from the master key
refresh['key_id'] = str(master_key.key_id)
refresh['permissions'] = master_key.permissions
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
})

@ -0,0 +1,16 @@
"""
ASGI config for botzilla 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/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'botzilla.settings')
application = get_asgi_application()

@ -0,0 +1,170 @@
"""
Django settings for botzilla project.
Generated by 'django-admin startproject' using Django 5.2.1.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
from pathlib import Path
from datetime import timedelta
# 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/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'lpw-7tb-!hvdd(eb^v*e0viuoguhiewjlnkfsui8732fpd93emf02ngocbnob6h!$ihsqv)sv^y5k)j!)m_#+!v'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'api',
'drf_yasg',
]
# REST Framework settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60 * 12),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'BLACKLIST_AFTER_ROTATION': True,
}
SWAGGER_SETTINGS = {
'FAVICON_HREF': '/static/favicon.ico',
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
},
},
}
REDOC_SETTINGS = {
'LAZY_RENDERING': True,
'HIDE_HOSTNAME': False,
}
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 = 'botzilla.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'botzilla.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.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/5.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/5.2/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -0,0 +1,45 @@
"""
URL configuration for botzilla project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.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.contrib import admin
from django.urls import path, include
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="Botzilla API",
default_version='v1',
description="Botzilla API documentation",
terms_of_service="https://www.example.com/terms/",
contact=openapi.Contact(email="contact@botzilla.ch"),
license=openapi.License(name="BSD License"),
x_logo={
"url": "/static/logo.svg",
"backgroundColor": "#FFFFFF",
"altText": "Botzilla logo"
}
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

@ -0,0 +1,16 @@
"""
WSGI config for botzilla 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/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'botzilla.settings')
application = get_wsgi_application()

@ -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', 'botzilla.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,0 +1,25 @@
annotated-types==0.7.0
anyio==4.9.0
asgiref==3.8.1
certifi==2025.4.26
Django==5.2.1
djangorestframework==3.16.0
djangorestframework_simplejwt==5.5.0
drf-yasg==1.21.10
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
inflection==0.5.1
ollama==0.4.8
packaging==25.0
pydantic==2.11.4
pydantic_core==2.33.2
PyJWT==2.9.0
pytz==2025.2
PyYAML==6.0.2
sniffio==1.3.1
sqlparse==0.5.3
typing-inspection==0.4.0
typing_extensions==4.13.2
uritemplate==4.1.1

@ -0,0 +1,4 @@
img {
max-height: 20px;
width: auto;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 960c-92.8 0-160-200-160-448S419.2 64 512 64s160 200 160 448-67.2 448-160 448z m0-32c65.6 0 128-185.6 128-416S577.6 96 512 96s-128 185.6-128 416 62.4 416 128 416z" fill="#050D42" /><path d="M124.8 736c-48-80 92.8-238.4 307.2-363.2S852.8 208 899.2 288 806.4 526.4 592 651.2 171.2 816 124.8 736z m27.2-16c33.6 57.6 225.6 17.6 424-97.6S905.6 361.6 872 304 646.4 286.4 448 401.6 118.4 662.4 152 720z" fill="#050D42" /><path d="M899.2 736c-46.4 80-254.4 38.4-467.2-84.8S76.8 368 124.8 288s254.4-38.4 467.2 84.8S947.2 656 899.2 736z m-27.2-16c33.6-57.6-97.6-203.2-296-318.4S184 246.4 152 304 249.6 507.2 448 622.4s392 155.2 424 97.6z" fill="#050D42" /><path d="M512 592c-44.8 0-80-35.2-80-80s35.2-80 80-80 80 35.2 80 80-35.2 80-80 80zM272 312c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48zM416 880c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48z m448-432c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48z" fill="#2F4BFF" /></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Loading…
Cancel
Save