implement custom user
This commit is contained in:
parent
c70a2f1aeb
commit
2b4d18a222
|
@ -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/
|
||||||
|
|
||||||
|
|
|
@ -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")),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
|
@ -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()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -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)
|
||||||
|
|
|
@ -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 %}
|
|
@ -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>© 2023 AnnoMemo. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue