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
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
|
|
|
@ -21,5 +21,6 @@ from django.urls import path, include
|
|||
urlpatterns = [
|
||||
path("", include("webui.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
|
||||
|
||||
# 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 = [
|
||||
path("", views.index, name="index"),
|
||||
path("register", views.register, name="register"),
|
||||
]
|
||||
|
|
|
@ -1,6 +1,48 @@
|
|||
from django.shortcuts import render
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, render
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
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