implement custom user

This commit is contained in:
James Ravenscroft 2024-11-24 07:45:02 +00:00
parent c70a2f1aeb
commit 2b4d18a222
11 changed files with 303 additions and 4 deletions

View File

@ -101,6 +101,10 @@ AUTH_PASSWORD_VALIDATORS = [
] ]
AUTH_USER_MODEL = 'webui.User'
AUTHENTICATION_BACKENDS = ['webui.auth.EmailBackend']
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/ # https://docs.djangoproject.com/en/4.2/topics/i18n/

View File

@ -21,5 +21,6 @@ from django.urls import path, include
urlpatterns = [ urlpatterns = [
path("", include("webui.urls")), path("", include("webui.urls")),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
] ]

15
penparse/webui/auth.py Normal file
View File

@ -0,0 +1,15 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password): # type: ignore
return user
return None

View File

@ -0,0 +1,44 @@
# Generated by Django 4.2.16 on 2024-11-23 13:03
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True)),
('password', models.CharField(blank=True, max_length=256)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -1,3 +1,7 @@
from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
# Create your models here.
class User(AbstractUser):
email = models.EmailField(unique=True)
password = models.CharField(max_length=256, blank=True)

View File

@ -0,0 +1,61 @@
{% extends "main.html" %} {% block content %}
<section class="text-center mb-16">
<h1 class="text-4xl font-bold text-gray-800 mb-4">
Transform Handwritten Notes into Digital Text
</h1>
<p class="text-xl text-gray-600 mb-8">
AnnoMemo uses intelligent OCR to digitize your handwritten notes and
integrate them with your favorite PKM tools.
</p>
<a
href="{% url 'register' %}"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300"
>Get Started</a
>
</section>
<section id="features" class="mb-16">
<h2 class="text-3xl font-bold text-gray-800 mb-6">Features</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-xl font-semibold mb-2">Intelligent OCR</h3>
<p class="text-gray-600">
Accurately convert handwritten notes to digital text
</p>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-xl font-semibold mb-2">PKM Integration</h3>
<p class="text-gray-600">
Seamlessly integrate with Obsidian, Memos, and Joplin
</p>
</div>
<div class="bg-white p-6 rounded-lg shadow">
<h3 class="text-xl font-semibold mb-2">Bot Support</h3>
<p class="text-gray-600">Process photos via Telegram and Discord bots</p>
</div>
</div>
</section>
<section id="integrations" class="mb-16">
<h2 class="text-3xl font-bold text-gray-800 mb-6">Integrations</h2>
<div class="flex flex-wrap justify-center gap-8">
<img src="path_to_obsidian_logo.png" alt="Obsidian" class="h-12" />
<img src="path_to_memos_logo.png" alt="Memos" class="h-12" />
<img src="path_to_joplin_logo.png" alt="Joplin" class="h-12" />
<img src="path_to_telegram_logo.png" alt="Telegram" class="h-12" />
<img src="path_to_discord_logo.png" alt="Discord" class="h-12" />
</div>
</section>
<section id="pricing" class="text-center">
<h2 class="text-3xl font-bold text-gray-800 mb-6">Simple Pricing</h2>
<p class="text-xl text-gray-600 mb-8">
Start for free, upgrade when you need more
</p>
<a
href="#"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition duration-300"
>View Pricing Plans</a
>
</section>
{% endblock %}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AnnoMemo - Intelligent OCR for Handwritten Notes</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 font-sans">
<header class="bg-white shadow-sm">{% include "partial/nav.html" %}</header>
<main class="container mx-auto px-6 py-8">
{%block content%} {%endblock%}
</main>
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-6 text-center">
<p>&copy; 2023 AnnoMemo. All rights reserved.</p>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,14 @@
<nav class="container mx-auto px-6 py-3 flex justify-between items-center">
<div class="text-2xl font-bold text-gray-800"><a href="/">AnnoMemo</a></div>
<div>
<a href="#features" class="text-gray-600 hover:text-gray-800 px-3 py-2"
>Features</a
>
<a href="#integrations" class="text-gray-600 hover:text-gray-800 px-3 py-2"
>Integrations</a
>
<a href="#pricing" class="text-gray-600 hover:text-gray-800 px-3 py-2"
>Pricing</a
>
</div>
</nav>

View File

@ -0,0 +1,91 @@
{% extends "main.html" %} {% block content %}
<main class="container mx-auto px-6 py-8">
<div class="max-w-md mx-auto bg-white rounded-lg overflow-hidden shadow-lg">
<div class="px-6 py-8">
<h2 class="text-2xl font-bold text-center text-gray-800 mb-8">
Create Your AnnoMemo Account
</h2>
{% if messages %} {% for message in messages %}
<div
class="mb-4 p-4 {% if message.tags == 'error' %}bg-red-100 text-red-700{% else %}bg-green-100 text-green-700{% endif %} rounded"
>
{{ message }}
</div>
{% endfor %} {% endif %}
<form method="POST" action="/auth/register">
{% csrf_token %}
<div class="mb-4">
<label
for="fullName"
class="block text-gray-700 text-sm font-bold mb-2"
>Full Name</label
>
<input
type="text"
id="fullName"
name="fullName"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value="{{ form.fullName.value|default_if_none:'' }}"
required
/>
</div>
<div class="mb-4">
<label for="email" class="block text-gray-700 text-sm font-bold mb-2"
>Email Address</label
>
<input
type="email"
id="email"
name="email"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value="{{ form.email.value|default_if_none:'' }}"
required
/>
</div>
<div class="mb-4">
<label
for="password"
class="block text-gray-700 text-sm font-bold mb-2"
>Password</label
>
<input
type="password"
id="password"
name="password"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
required
/>
</div>
<div class="mb-6">
<label
for="confirmPassword"
class="block text-gray-700 text-sm font-bold mb-2"
>Confirm Password</label
>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
required
/>
</div>
<div class="flex items-center justify-between">
<button
type="submit"
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
>
Create Account
</button>
</div>
</form>
</div>
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200">
<p class="text-center text-gray-600 text-sm">
Already have an account?
<a href="#" class="text-blue-500 hover:text-blue-600">Sign in</a>
</p>
</div>
</div>
</main>
{% endblock %}

View File

@ -4,4 +4,5 @@ from . import views
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("register", views.register, name="register"),
] ]

View File

@ -1,6 +1,48 @@
from django.shortcuts import render from django.contrib import messages
from django.http import HttpResponse from django.shortcuts import redirect, render
from django.http import HttpRequest, HttpResponse
from .models import User
def index(request): def index(request):
return HttpResponse("Hello, world. You're at the polls index.") # return HttpResponse("Hello, world. You're at the polls index.")
return render(request, 'index.html')
def register(request: HttpRequest):
# if the form is not submitted yet, return the form
if request.method != 'POST':
return render(request, 'register.html', {'errors': False})
email = request.POST.get('email')
password = request.POST.get('password')
confirm_password = request.POST.get('confirm_password')
errors = False
if not email:
messages.error(request, 'Email is required')
errors = True
if not password or len(password) < 8:
messages.error(request, 'Password must be at least 8 characters long')
errors = True
if password != confirm_password:
messages.error(request, 'Passwords do not match')
errors = True
if not errors:
if User.objects.filter(email=email).exists():
messages.error(request, 'Email already exists')
else:
user = User.objects.create_user(
username=username, email=email, password=password) # type: ignore
user.save()
messages.success(request, 'Account created successfully')
return redirect('login')
return render(request, 'register.html')