diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f67e8cf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+“Commons Clause” License Condition v1.0
+
+The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
+
+Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software.
+
+For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
+
+Software: Vidura ChatGPT Prompt System
+
+License: GNU GPL 3.0
+
+Licensor: HappyPythonist.com
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..311ed64
--- /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', 'vidura.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/promptbook/__init__.py b/promptbook/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/promptbook/admin.py b/promptbook/admin.py
new file mode 100644
index 0000000..da66711
--- /dev/null
+++ b/promptbook/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+from .models import Category, Prompt, Label, PromptLabel
+
+# Register your models here.
+
+admin.site.register([Category, Prompt, Label, PromptLabel])
\ No newline at end of file
diff --git a/promptbook/apps.py b/promptbook/apps.py
new file mode 100644
index 0000000..df42148
--- /dev/null
+++ b/promptbook/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PromptbookConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'promptbook'
diff --git a/promptbook/migrations/0001_initial.py b/promptbook/migrations/0001_initial.py
new file mode 100644
index 0000000..49c634e
--- /dev/null
+++ b/promptbook/migrations/0001_initial.py
@@ -0,0 +1,44 @@
+# Generated by Django 4.1.7 on 2023-04-02 03:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('modified_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Prompt',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('text', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('modified_at', models.DateTimeField(auto_now=True)),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='promptbook.category')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Label',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('modified_at', models.DateTimeField(auto_now=True)),
+ ('category', models.ManyToManyField(to='promptbook.category')),
+ ],
+ ),
+ ]
diff --git a/promptbook/migrations/0002_alter_label_category.py b/promptbook/migrations/0002_alter_label_category.py
new file mode 100644
index 0000000..69e6b76
--- /dev/null
+++ b/promptbook/migrations/0002_alter_label_category.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.7 on 2023-04-02 05:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('promptbook', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='label',
+ name='category',
+ field=models.ManyToManyField(to='promptbook.prompt'),
+ ),
+ ]
diff --git a/promptbook/migrations/0003_remove_label_category_promptlabel.py b/promptbook/migrations/0003_remove_label_category_promptlabel.py
new file mode 100644
index 0000000..36f1dc6
--- /dev/null
+++ b/promptbook/migrations/0003_remove_label_category_promptlabel.py
@@ -0,0 +1,26 @@
+# Generated by Django 4.1.7 on 2023-04-02 05:27
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('promptbook', '0002_alter_label_category'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='label',
+ name='category',
+ ),
+ migrations.CreateModel(
+ name='PromptLabel',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('label', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='promptbook.label')),
+ ('prompt', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='promptbook.prompt')),
+ ],
+ ),
+ ]
diff --git a/promptbook/migrations/__init__.py b/promptbook/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/promptbook/models.py b/promptbook/models.py
new file mode 100644
index 0000000..8c30757
--- /dev/null
+++ b/promptbook/models.py
@@ -0,0 +1,33 @@
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(max_length=255)
+ created_at = models.DateTimeField(auto_now_add=True)
+ modified_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return self.name
+
+class Prompt(models.Model):
+ text = models.TextField()
+ category = models.ForeignKey(Category, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+ modified_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return self.text[:50] + '...' if len(self.text) > 50 else self.text
+
+class Label(models.Model):
+ name = models.CharField(max_length=255)
+ created_at = models.DateTimeField(auto_now_add=True)
+ modified_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return self.name
+
+class PromptLabel(models.Model):
+ label = models.ForeignKey(Label, on_delete=models.DO_NOTHING)
+ prompt = models.ForeignKey(Prompt, on_delete=models.DO_NOTHING)
+
+ def __str__(self):
+ return f"Label: {self.label.__str__()}, Prompt: {self.prompt.__str__()}"
diff --git a/promptbook/templates/base.html b/promptbook/templates/base.html
new file mode 100644
index 0000000..68bb2f2
--- /dev/null
+++ b/promptbook/templates/base.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Vidura
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
diff --git a/promptbook/templates/list_categories.html b/promptbook/templates/list_categories.html
new file mode 100644
index 0000000..519d1c4
--- /dev/null
+++ b/promptbook/templates/list_categories.html
@@ -0,0 +1,16 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Categories
+
+ {% for category in categories %}
+
+ {% endfor %}
+
+{% endblock %}
diff --git a/promptbook/templates/list_prompts.html b/promptbook/templates/list_prompts.html
new file mode 100644
index 0000000..5ff1875
--- /dev/null
+++ b/promptbook/templates/list_prompts.html
@@ -0,0 +1,32 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Prompts in category: {{ category.name }}
+
+ {% for prompt in prompts %}
+
+
+
+
{{ prompt.text|truncatechars:250 }} Length: {{ prompt.text|length }} chars...
+
+ {{ prompt_labels }}
+ {% for label in prompt_labels.prompt.id %}
+ {{ label }}
+ {% endfor %}
+
+
+
+
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/promptbook/templates/login.html b/promptbook/templates/login.html
new file mode 100644
index 0000000..e69de29
diff --git a/promptbook/tests.py b/promptbook/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/promptbook/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/promptbook/urls.py b/promptbook/urls.py
new file mode 100644
index 0000000..5b2fa23
--- /dev/null
+++ b/promptbook/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+ path('login/', views.login, name='login'),
+ path('categories/', views.list_categories, name='list_categories'),
+ path('categories//prompts/', views.list_prompts, name='list_prompts'),
+ path('logout/', views.logout, name='logout'),
+]
diff --git a/promptbook/views.py b/promptbook/views.py
new file mode 100644
index 0000000..8e18b02
--- /dev/null
+++ b/promptbook/views.py
@@ -0,0 +1,45 @@
+from django.shortcuts import render
+from .models import Category, Prompt, PromptLabel
+from django.contrib.auth import login as auth_login
+from django.contrib.auth import logout as auth_logout
+from django.contrib.auth.forms import AuthenticationForm
+from django.shortcuts import render, redirect
+
+def list_categories(request):
+ categories = Category.objects.all()
+ context = {'categories': categories}
+ return render(request, 'list_categories.html', context)
+
+def list_prompts(request, category_id):
+ category = Category.objects.get(pk=category_id)
+ prompts = category.prompt_set.all()
+
+ prompt_labels = {}
+ for prompt in prompts:
+ labels = [pl.label for pl in PromptLabel.objects.filter(prompt=prompt)]
+ prompt_labels[prompt.id] = labels
+
+ return render(request, 'list_prompts.html', {
+ 'category': category,
+ 'prompts': prompts,
+ 'prompt_labels': prompt_labels,
+ })
+
+def login(request):
+ if request.user.is_authenticated:
+ return redirect('list_categories')
+
+ if request.method == 'POST':
+ form = AuthenticationForm(request, data=request.POST)
+ if form.is_valid():
+ auth_login(request, form.get_user())
+ return redirect('list_categories')
+ else:
+ form = AuthenticationForm(request)
+ return render(request, 'login.html', {'form': form})
+
+
+
+def logout(request):
+ auth_logout(request)
+ return redirect('login')
diff --git a/vidura/__init__.py b/vidura/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/vidura/asgi.py b/vidura/asgi.py
new file mode 100644
index 0000000..d942b89
--- /dev/null
+++ b/vidura/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for vidura 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.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'vidura.settings')
+
+application = get_asgi_application()
diff --git a/vidura/settings.py b/vidura/settings.py
new file mode 100644
index 0000000..06126c3
--- /dev/null
+++ b/vidura/settings.py
@@ -0,0 +1,130 @@
+"""
+Django settings for vidura project.
+
+Generated by 'django-admin startproject' using Django 4.1.7.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/4.1/ref/settings/
+"""
+
+from pathlib import Path
+
+# 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.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-jjzo#gr%31zskhnyx_ktcy_85_fl^)5h@&*tp97+n#xy#ckb*o'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+AUTHENTICATION_BACKENDS = [
+ 'django.contrib.auth.backends.ModelBackend',
+]
+
+LOGIN_REDIRECT_URL = 'list_categories'
+LOGOUT_REDIRECT_URL = 'login'
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'promptbook'
+]
+
+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 = 'vidura.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ '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 = 'vidura.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/4.1/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.1/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.1/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
diff --git a/vidura/urls.py b/vidura/urls.py
new file mode 100644
index 0000000..77babbc
--- /dev/null
+++ b/vidura/urls.py
@@ -0,0 +1,22 @@
+"""vidura URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/4.1/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
+
+urlpatterns = [
+ path('home/', include('promptbook.urls')),
+ path('admin/', admin.site.urls),
+]
diff --git a/vidura/wsgi.py b/vidura/wsgi.py
new file mode 100644
index 0000000..1691f6d
--- /dev/null
+++ b/vidura/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for vidura 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.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'vidura.settings')
+
+application = get_wsgi_application()