Spaces:
Configuration error
Configuration error
Updated
Browse files- README.md +1 -10
- accounts/admin.py +48 -1
- accounts/migrations/0001_initial.py +44 -0
- accounts/migrations/0002_alter_user_options_alter_user_managers_and_more.py +104 -0
- accounts/models.py +109 -1
- accounts/serializers.py +26 -13
- accounts/task.py +56 -0
- accounts/urls.py +6 -4
- accounts/views.py +93 -9
- config/schemaprotocol.py +6 -0
- config/settings.py +20 -4
- config/urls.py +2 -1
- db.sqlite3 +0 -0
- project.log +0 -0
- texttovoice/models.py +1 -1
- texttovoice/serializers.py +4 -4
README.md
CHANGED
@@ -1,11 +1,2 @@
|
|
1 |
-
---
|
2 |
-
title: Voice Clone
|
3 |
-
emoji: 🦀
|
4 |
-
colorFrom: green
|
5 |
-
colorTo: purple
|
6 |
-
sdk: docker
|
7 |
-
pinned: false
|
8 |
-
license: mit
|
9 |
-
---
|
10 |
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
+
title: Voice Clone
|
accounts/admin.py
CHANGED
@@ -1,3 +1,50 @@
|
|
1 |
from django.contrib import admin
|
|
|
2 |
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from django.contrib import admin
|
2 |
+
from django.contrib.auth.admin import UserAdmin
|
3 |
|
4 |
+
from .models import User
|
5 |
+
|
6 |
+
|
7 |
+
class AdminUser(UserAdmin):
|
8 |
+
list_display = ["email", "first_name", "last_name", "role", "is_active", "created"]
|
9 |
+
|
10 |
+
fieldsets = (
|
11 |
+
(
|
12 |
+
None,
|
13 |
+
{
|
14 |
+
"fields": (
|
15 |
+
"first_name",
|
16 |
+
"last_name",
|
17 |
+
"mobile",
|
18 |
+
"email",
|
19 |
+
"password",
|
20 |
+
)
|
21 |
+
},
|
22 |
+
),
|
23 |
+
(
|
24 |
+
"Permissions",
|
25 |
+
{
|
26 |
+
"fields": (
|
27 |
+
"role",
|
28 |
+
"is_active",
|
29 |
+
"is_staff",
|
30 |
+
"is_superuser",
|
31 |
+
"user_permissions",
|
32 |
+
)
|
33 |
+
},
|
34 |
+
),
|
35 |
+
)
|
36 |
+
add_fieldsets = (
|
37 |
+
(None, {"classes": ("wide",), "fields": ("email", "password1", "password2")}),
|
38 |
+
)
|
39 |
+
|
40 |
+
list_filter = ("role", "is_active")
|
41 |
+
search_fields = ("email", "first_name", "last_name")
|
42 |
+
ordering = ("email",)
|
43 |
+
filter_horizontal = (
|
44 |
+
"groups",
|
45 |
+
"user_permissions",
|
46 |
+
)
|
47 |
+
|
48 |
+
|
49 |
+
admin.site.register(User, AdminUser)
|
50 |
+
# admin.site.unregister(Group)
|
accounts/migrations/0001_initial.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Generated by Django 5.0.1 on 2024-04-02 09:30
|
2 |
+
|
3 |
+
import django.contrib.auth.models
|
4 |
+
import django.contrib.auth.validators
|
5 |
+
import django.utils.timezone
|
6 |
+
from django.db import migrations, models
|
7 |
+
|
8 |
+
|
9 |
+
class Migration(migrations.Migration):
|
10 |
+
|
11 |
+
initial = True
|
12 |
+
|
13 |
+
dependencies = [
|
14 |
+
('auth', '0012_alter_user_first_name_max_length'),
|
15 |
+
]
|
16 |
+
|
17 |
+
operations = [
|
18 |
+
migrations.CreateModel(
|
19 |
+
name='User',
|
20 |
+
fields=[
|
21 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
22 |
+
('password', models.CharField(max_length=128, verbose_name='password')),
|
23 |
+
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
24 |
+
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
25 |
+
('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')),
|
26 |
+
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
27 |
+
('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')),
|
28 |
+
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
29 |
+
('email', models.EmailField(max_length=254, unique=True)),
|
30 |
+
('first_name', models.CharField(max_length=150)),
|
31 |
+
('last_name', models.CharField(max_length=150)),
|
32 |
+
('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')),
|
33 |
+
('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')),
|
34 |
+
],
|
35 |
+
options={
|
36 |
+
'verbose_name': 'user',
|
37 |
+
'verbose_name_plural': 'users',
|
38 |
+
'abstract': False,
|
39 |
+
},
|
40 |
+
managers=[
|
41 |
+
('objects', django.contrib.auth.models.UserManager()),
|
42 |
+
],
|
43 |
+
),
|
44 |
+
]
|
accounts/migrations/0002_alter_user_options_alter_user_managers_and_more.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Generated by Django 5.0.1 on 2024-04-02 09:44
|
2 |
+
|
3 |
+
import django.core.validators
|
4 |
+
import django.utils.timezone
|
5 |
+
from django.db import migrations, models
|
6 |
+
|
7 |
+
|
8 |
+
class Migration(migrations.Migration):
|
9 |
+
|
10 |
+
dependencies = [
|
11 |
+
('accounts', '0001_initial'),
|
12 |
+
]
|
13 |
+
|
14 |
+
operations = [
|
15 |
+
migrations.AlterModelOptions(
|
16 |
+
name='user',
|
17 |
+
options={'verbose_name': 'User', 'verbose_name_plural': 'Users'},
|
18 |
+
),
|
19 |
+
migrations.AlterModelManagers(
|
20 |
+
name='user',
|
21 |
+
managers=[
|
22 |
+
],
|
23 |
+
),
|
24 |
+
migrations.RemoveField(
|
25 |
+
model_name='user',
|
26 |
+
name='username',
|
27 |
+
),
|
28 |
+
migrations.AddField(
|
29 |
+
model_name='user',
|
30 |
+
name='address',
|
31 |
+
field=models.TextField(blank=True, null=True, verbose_name='Address'),
|
32 |
+
),
|
33 |
+
migrations.AddField(
|
34 |
+
model_name='user',
|
35 |
+
name='city',
|
36 |
+
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='City/Town'),
|
37 |
+
),
|
38 |
+
migrations.AddField(
|
39 |
+
model_name='user',
|
40 |
+
name='country',
|
41 |
+
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Country'),
|
42 |
+
),
|
43 |
+
migrations.AddField(
|
44 |
+
model_name='user',
|
45 |
+
name='created',
|
46 |
+
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
47 |
+
preserve_default=False,
|
48 |
+
),
|
49 |
+
migrations.AddField(
|
50 |
+
model_name='user',
|
51 |
+
name='mobile',
|
52 |
+
field=models.CharField(blank=True, max_length=17, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.", regex='^\\+?1?\\d{9,15}$')], verbose_name='mobile'),
|
53 |
+
),
|
54 |
+
migrations.AddField(
|
55 |
+
model_name='user',
|
56 |
+
name='modified',
|
57 |
+
field=models.DateTimeField(auto_now=True),
|
58 |
+
),
|
59 |
+
migrations.AddField(
|
60 |
+
model_name='user',
|
61 |
+
name='role',
|
62 |
+
field=models.PositiveSmallIntegerField(choices=[(1, 'Admin'), (2, 'User')], default=2, verbose_name='role'),
|
63 |
+
),
|
64 |
+
migrations.AddField(
|
65 |
+
model_name='user',
|
66 |
+
name='state',
|
67 |
+
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='State/Province'),
|
68 |
+
),
|
69 |
+
migrations.AddField(
|
70 |
+
model_name='user',
|
71 |
+
name='zip_code',
|
72 |
+
field=models.CharField(blank=True, max_length=50, null=True),
|
73 |
+
),
|
74 |
+
migrations.AlterField(
|
75 |
+
model_name='user',
|
76 |
+
name='date_joined',
|
77 |
+
field=models.DateTimeField(auto_now_add=True, verbose_name='date joined'),
|
78 |
+
),
|
79 |
+
migrations.AlterField(
|
80 |
+
model_name='user',
|
81 |
+
name='email',
|
82 |
+
field=models.EmailField(max_length=254, unique=True, verbose_name='email address'),
|
83 |
+
),
|
84 |
+
migrations.AlterField(
|
85 |
+
model_name='user',
|
86 |
+
name='first_name',
|
87 |
+
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='First Name'),
|
88 |
+
),
|
89 |
+
migrations.AlterField(
|
90 |
+
model_name='user',
|
91 |
+
name='is_active',
|
92 |
+
field=models.BooleanField(default=True),
|
93 |
+
),
|
94 |
+
migrations.AlterField(
|
95 |
+
model_name='user',
|
96 |
+
name='is_staff',
|
97 |
+
field=models.BooleanField(default=False),
|
98 |
+
),
|
99 |
+
migrations.AlterField(
|
100 |
+
model_name='user',
|
101 |
+
name='last_name',
|
102 |
+
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Last Name'),
|
103 |
+
),
|
104 |
+
]
|
accounts/models.py
CHANGED
@@ -1,3 +1,111 @@
|
|
|
|
|
|
|
|
1 |
from django.db import models
|
|
|
|
|
2 |
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Create your models here.
|
2 |
+
from django.contrib.auth.base_user import BaseUserManager
|
3 |
+
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
|
4 |
from django.db import models
|
5 |
+
from django.utils.translation import gettext_lazy as _
|
6 |
+
from django.core.validators import RegexValidator
|
7 |
|
8 |
+
from .task import send_email_to_user
|
9 |
+
|
10 |
+
|
11 |
+
class BaseModel(models.Model):
|
12 |
+
created = models.DateTimeField(auto_now_add=True, editable=False)
|
13 |
+
modified = models.DateTimeField(auto_now=True, editable=False)
|
14 |
+
|
15 |
+
class Meta:
|
16 |
+
abstract = True
|
17 |
+
|
18 |
+
|
19 |
+
class UserManager(BaseUserManager):
|
20 |
+
"""
|
21 |
+
Custom user model manager where email is the unique identifiers
|
22 |
+
for authentication instead of usernames.
|
23 |
+
"""
|
24 |
+
|
25 |
+
def create_user(self, email, password, **extra_fields):
|
26 |
+
"""
|
27 |
+
Create and save a User with the given email and password.
|
28 |
+
"""
|
29 |
+
if not email:
|
30 |
+
raise ValueError(_("The Email must be set"))
|
31 |
+
email = self.normalize_email(email)
|
32 |
+
user = self.model(email=email, **extra_fields)
|
33 |
+
user.set_password(password)
|
34 |
+
user.save()
|
35 |
+
return user
|
36 |
+
|
37 |
+
def create_superuser(self, email, password, **extra_fields):
|
38 |
+
"""
|
39 |
+
Create and save a SuperUser with the given email and password.
|
40 |
+
"""
|
41 |
+
extra_fields.setdefault("is_staff", True)
|
42 |
+
extra_fields.setdefault("is_superuser", True)
|
43 |
+
extra_fields.setdefault("is_active", True)
|
44 |
+
|
45 |
+
if extra_fields.get("is_staff") is not True:
|
46 |
+
raise ValueError(_("Superuser must have is_staff=True."))
|
47 |
+
if extra_fields.get("is_superuser") is not True:
|
48 |
+
raise ValueError(_("Superuser must have is_superuser=True."))
|
49 |
+
return self.create_user(email, password, **extra_fields)
|
50 |
+
|
51 |
+
|
52 |
+
phone_regex = RegexValidator(
|
53 |
+
regex=r"^\+?1?\d{9,15}$",
|
54 |
+
message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.",
|
55 |
+
)
|
56 |
+
|
57 |
+
|
58 |
+
class User(AbstractBaseUser, PermissionsMixin):
|
59 |
+
"""Custom user model to add more fields"""
|
60 |
+
|
61 |
+
# User roles
|
62 |
+
class Roles(models.IntegerChoices):
|
63 |
+
"""Roles for user model"""
|
64 |
+
|
65 |
+
ADMIN = 1
|
66 |
+
USER = 2
|
67 |
+
|
68 |
+
email = models.EmailField(_("email address"), unique=True)
|
69 |
+
first_name = models.CharField(_("First Name"), max_length=50, blank=True, null=True)
|
70 |
+
last_name = models.CharField(_("Last Name"), max_length=50, blank=True, null=True)
|
71 |
+
|
72 |
+
mobile = models.CharField(
|
73 |
+
_("mobile"),
|
74 |
+
max_length=17,
|
75 |
+
validators=[phone_regex],
|
76 |
+
null=True,
|
77 |
+
blank=True,
|
78 |
+
)
|
79 |
+
|
80 |
+
address = models.TextField(_("Address"), blank=True, null=True)
|
81 |
+
city = models.CharField(_("City/Town"), max_length=50, blank=True, null=True)
|
82 |
+
state = models.CharField(_("State/Province"), max_length=50, blank=True, null=True)
|
83 |
+
country = models.CharField(_("Country"), max_length=50, blank=True, null=True)
|
84 |
+
zip_code = models.CharField(max_length=50, blank=True, null=True)
|
85 |
+
|
86 |
+
is_staff = models.BooleanField(default=False)
|
87 |
+
is_active = models.BooleanField(default=True)
|
88 |
+
date_joined = models.DateTimeField(_("date joined"), auto_now_add=True)
|
89 |
+
role = models.PositiveSmallIntegerField(
|
90 |
+
_("role"), choices=Roles.choices, default=Roles.USER
|
91 |
+
)
|
92 |
+
created = models.DateTimeField(auto_now_add=True, editable=False)
|
93 |
+
modified = models.DateTimeField(auto_now=True, editable=False)
|
94 |
+
|
95 |
+
USERNAME_FIELD = "email"
|
96 |
+
REQUIRED_FIELDS = []
|
97 |
+
|
98 |
+
objects = UserManager()
|
99 |
+
|
100 |
+
class Meta:
|
101 |
+
verbose_name = _("User")
|
102 |
+
verbose_name_plural = _("Users")
|
103 |
+
|
104 |
+
def __str__(self):
|
105 |
+
return self.get_full_name()
|
106 |
+
|
107 |
+
def get_full_name(self):
|
108 |
+
return "{} {}".format(self.first_name, self.last_name)
|
109 |
+
|
110 |
+
def send_forgot_password_email(self,host,email):
|
111 |
+
return send_email_to_user(self.pk, host,email,email_type="forgot_password")
|
accounts/serializers.py
CHANGED
@@ -1,20 +1,33 @@
|
|
1 |
from rest_framework import serializers
|
2 |
-
from
|
3 |
|
4 |
-
class
|
5 |
password = serializers.CharField(write_only=True)
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
def create(self, validated_data):
|
8 |
-
|
9 |
-
user = User.objects.create_user(
|
10 |
-
username=validated_data['username'],
|
11 |
-
email=validated_data.get('email', ''), # Default to empty string if email is not provided
|
12 |
-
password=validated_data['password'],
|
13 |
-
first_name=validated_data.get('first_name', ''), # Default to empty string if first_name is not provided
|
14 |
-
last_name=validated_data.get('last_name', '') # Default to empty string if last_name is not provided
|
15 |
-
)
|
16 |
return user
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from rest_framework import serializers
|
2 |
+
from .models import User
|
3 |
|
4 |
+
class UserRegistrationSerializer(serializers.ModelSerializer):
|
5 |
password = serializers.CharField(write_only=True)
|
6 |
|
7 |
+
class Meta:
|
8 |
+
model = User
|
9 |
+
fields = ['email', 'password', 'first_name', 'last_name', 'mobile', 'address', 'city', 'state', 'country', 'zip_code', 'role']
|
10 |
+
extra_kwargs = {
|
11 |
+
'first_name': {'required': False},
|
12 |
+
'last_name': {'required': False},
|
13 |
+
'mobile': {'required': False},
|
14 |
+
'address': {'required': False},
|
15 |
+
'city': {'required': False},
|
16 |
+
'state': {'required': False},
|
17 |
+
'country': {'required': False},
|
18 |
+
'zip_code': {'required': False},
|
19 |
+
'role': {'required': False},
|
20 |
+
}
|
21 |
+
|
22 |
def create(self, validated_data):
|
23 |
+
user = User.objects.create_user(**validated_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
return user
|
25 |
|
26 |
+
|
27 |
+
class ForgotPasswordSerializer(serializers.Serializer):
|
28 |
+
email = serializers.EmailField()
|
29 |
+
|
30 |
+
class ResetPasswordSerializer(serializers.Serializer):
|
31 |
+
token = serializers.CharField()
|
32 |
+
uid = serializers.CharField()
|
33 |
+
new_password = serializers.CharField()
|
accounts/task.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
2 |
+
from django.core.mail import send_mail
|
3 |
+
from django.conf import settings
|
4 |
+
from django.contrib.auth import get_user_model
|
5 |
+
from django.utils.encoding import force_bytes
|
6 |
+
from django.utils.html import strip_tags
|
7 |
+
from django.utils.http import urlsafe_base64_encode
|
8 |
+
|
9 |
+
|
10 |
+
def send_email_to_user(
|
11 |
+
user_pk,
|
12 |
+
host,
|
13 |
+
email,
|
14 |
+
email_type,
|
15 |
+
new_email=None,
|
16 |
+
from_email=settings.EMAIL_HOST_USER,
|
17 |
+
**kwargs,
|
18 |
+
):
|
19 |
+
user = get_user_model().objects.get(pk=user_pk)
|
20 |
+
|
21 |
+
token_generator = PasswordResetTokenGenerator()
|
22 |
+
token = token_generator.make_token(user)
|
23 |
+
uid = urlsafe_base64_encode(force_bytes(user.pk))
|
24 |
+
reset_link = f"{host}/verify/{uid}/{token}"
|
25 |
+
|
26 |
+
# Define your email subject
|
27 |
+
subject = "Reset Your Password for Text to Speech AI Voice Generator"
|
28 |
+
|
29 |
+
# Write the HTML content directly in the function
|
30 |
+
html_content = f"""
|
31 |
+
<html>
|
32 |
+
<body>
|
33 |
+
<p>Hello {user.get_full_name() or user.username},</p>
|
34 |
+
<p>You requested a password reset for your account. Click the link below to reset your password:</p>
|
35 |
+
<p><a href="{reset_link}">Reset Password</a></p>
|
36 |
+
<p>If you didn't request this, you can ignore this email.</p>
|
37 |
+
<p>Your link will expire in 48 hours.</p>
|
38 |
+
<p>Thank you!</p>
|
39 |
+
</body>
|
40 |
+
</html>
|
41 |
+
"""
|
42 |
+
|
43 |
+
# Create a text-only version of the email
|
44 |
+
text_content = strip_tags(html_content)
|
45 |
+
|
46 |
+
try:
|
47 |
+
send_mail(
|
48 |
+
subject,
|
49 |
+
text_content,
|
50 |
+
from_email,
|
51 |
+
[email],
|
52 |
+
html_message=html_content, # This is the key part for HTML
|
53 |
+
fail_silently=False,
|
54 |
+
)
|
55 |
+
except Exception as e:
|
56 |
+
print(e)
|
accounts/urls.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
from django.urls import path
|
2 |
-
from . import
|
3 |
-
from rest_framework.authtoken.views import obtain_auth_token
|
4 |
|
5 |
urlpatterns = [
|
6 |
-
path('register/',
|
7 |
-
path('login',
|
|
|
|
|
|
|
8 |
]
|
9 |
|
|
|
1 |
from django.urls import path
|
2 |
+
from .views import *
|
|
|
3 |
|
4 |
urlpatterns = [
|
5 |
+
path('register/', UserRegistrationAPIView.as_view(), name='user-registration'),
|
6 |
+
path('login/', LoginView.as_view(), name='login'),
|
7 |
+
path('forgot-password/', ResetPasswordView.as_view(), name='forgot_password'),
|
8 |
+
path('reset-password/', ChangePasswordView.as_view(), name='reset_password'),
|
9 |
+
|
10 |
]
|
11 |
|
accounts/views.py
CHANGED
@@ -1,16 +1,100 @@
|
|
1 |
-
from .
|
2 |
from rest_framework import status
|
3 |
from rest_framework.response import Response
|
4 |
-
from rest_framework.generics import CreateAPIView
|
5 |
from rest_framework.authtoken.models import Token
|
6 |
-
from
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
def perform_create(self, serializer):
|
13 |
-
user = serializer.save()
|
14 |
token, _ = Token.objects.get_or_create(user=user)
|
15 |
-
return Response({'token': token.key}, status=status.HTTP_201_CREATED)
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .models import User
|
2 |
from rest_framework import status
|
3 |
from rest_framework.response import Response
|
4 |
+
from rest_framework.generics import CreateAPIView,UpdateAPIView
|
5 |
from rest_framework.authtoken.models import Token
|
6 |
+
from rest_framework.views import APIView
|
7 |
+
from django.contrib.auth import authenticate
|
8 |
+
from drf_yasg.utils import swagger_auto_schema
|
9 |
+
from drf_yasg import openapi
|
10 |
+
from django.utils.http import urlsafe_base64_decode
|
11 |
+
from django.utils.encoding import force_str
|
12 |
|
13 |
+
from .serializers import ForgotPasswordSerializer, ResetPasswordSerializer, UserRegistrationSerializer
|
14 |
+
from django.contrib.auth.tokens import default_token_generator
|
15 |
+
|
16 |
+
|
17 |
+
class UserRegistrationAPIView(CreateAPIView):
|
18 |
+
serializer_class = UserRegistrationSerializer
|
19 |
+
def post(self, request, format=None):
|
20 |
+
serializer = UserRegistrationSerializer(data=request.data)
|
21 |
+
if serializer.is_valid():
|
22 |
+
serializer.save()
|
23 |
+
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
24 |
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
25 |
+
|
26 |
+
|
27 |
+
|
28 |
+
class LoginView(APIView):
|
29 |
+
@swagger_auto_schema(
|
30 |
+
request_body=openapi.Schema(
|
31 |
+
type=openapi.TYPE_OBJECT,
|
32 |
+
required=['email', 'password'],
|
33 |
+
properties={
|
34 |
+
'email': openapi.Schema(type=openapi.TYPE_STRING),
|
35 |
+
'password': openapi.Schema(type=openapi.TYPE_STRING),
|
36 |
+
},
|
37 |
+
),
|
38 |
+
responses={
|
39 |
+
status.HTTP_200_OK: openapi.Schema(
|
40 |
+
type=openapi.TYPE_OBJECT,
|
41 |
+
properties={
|
42 |
+
'token': openapi.Schema(type=openapi.TYPE_STRING),
|
43 |
+
},
|
44 |
+
),
|
45 |
+
},
|
46 |
+
)
|
47 |
+
def post(self, request):
|
48 |
+
email = request.data.get('email')
|
49 |
+
password = request.data.get('password')
|
50 |
+
|
51 |
+
if email is None or password is None:
|
52 |
+
return Response({'error': 'Please provide both email and password'}, status=status.HTTP_400_BAD_REQUEST)
|
53 |
+
|
54 |
+
user = authenticate(email=email, password=password)
|
55 |
+
|
56 |
+
if not user:
|
57 |
+
return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
|
58 |
|
|
|
|
|
59 |
token, _ = Token.objects.get_or_create(user=user)
|
|
|
60 |
|
61 |
+
return Response({'token': token.key})
|
62 |
+
|
63 |
+
|
64 |
+
class ResetPasswordView(CreateAPIView):
|
65 |
+
serializer_class = ForgotPasswordSerializer
|
66 |
+
|
67 |
+
def post(self, request):
|
68 |
+
serializer = self.serializer_class(data=request.data)
|
69 |
+
if serializer.is_valid():
|
70 |
+
email = serializer.validated_data.get("email")
|
71 |
+
if User.objects.filter(email=email).exists():
|
72 |
+
user = User.objects.get(email=email)
|
73 |
+
scheme = "https" if request.is_secure() else "http"
|
74 |
+
full_host = request.get_host()
|
75 |
+
base_url = f"{scheme}://{full_host}"
|
76 |
+
user.send_forgot_password_email(base_url, email)
|
77 |
+
return Response({"message": "Password reset email sent"})
|
78 |
+
else:
|
79 |
+
return Response({"error": "Email does not exist"}, status=status.HTTP_400_BAD_REQUEST)
|
80 |
+
else:
|
81 |
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
82 |
+
|
83 |
+
|
84 |
+
class ChangePasswordView(UpdateAPIView):
|
85 |
+
serializer_class = ResetPasswordSerializer
|
86 |
+
http_method_names = ['patch', 'head', 'options', 'trace']
|
87 |
+
|
88 |
+
def partial_update(self, request, *args, **kwargs):
|
89 |
+
serializer = self.get_serializer(data=request.data)
|
90 |
+
serializer.is_valid(raise_exception=True)
|
91 |
+
|
92 |
+
uid = force_str(urlsafe_base64_decode(serializer.validated_data['uid']))
|
93 |
+
user = User.objects.get(pk=uid)
|
94 |
+
|
95 |
+
if user is not None and default_token_generator.check_token(user, serializer.validated_data['token']):
|
96 |
+
user.set_password(serializer.validated_data['new_password'])
|
97 |
+
user.save()
|
98 |
+
return Response({"message": "Password updated successfully"}, status=status.HTTP_200_OK)
|
99 |
+
else:
|
100 |
+
return Response({"error": "Invalid token or user does not exist"}, status=status.HTTP_400_BAD_REQUEST)
|
config/schemaprotocol.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from drf_yasg.generators import OpenAPISchemaGenerator
|
2 |
+
class BothHttpAndHttpsSchemaGenerator(OpenAPISchemaGenerator):
|
3 |
+
def get_schema(self, request=None, public=False):
|
4 |
+
schema = super().get_schema(request, public)
|
5 |
+
schema.schemes = ["http", "https"]
|
6 |
+
return schema
|
config/settings.py
CHANGED
@@ -51,7 +51,8 @@ INSTALLED_APPS = [
|
|
51 |
CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
|
52 |
CORS_ALLOW_CREDENTIALS = True
|
53 |
CORS_ALLOWED_ORIGINS = [
|
54 |
-
'https://undetectable-voice-clone.hf.space', "http://94.101.98.237:7860" ,"https://voice-clone-frontend.vercel.app" ,
|
|
|
55 |
]
|
56 |
|
57 |
REST_FRAMEWORK = {
|
@@ -122,10 +123,12 @@ DATABASES = {
|
|
122 |
}
|
123 |
}
|
124 |
|
125 |
-
MINIO_ENDPOINT = '
|
126 |
MINIO_ACCESS_KEY = 'voice-clone'
|
127 |
MINIO_SECRET_KEY = 'voice-clone'
|
128 |
-
MINIO_SECURE =
|
|
|
|
|
129 |
|
130 |
# Password validation
|
131 |
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
@@ -157,7 +160,11 @@ USE_I18N = True
|
|
157 |
|
158 |
USE_TZ = True
|
159 |
|
160 |
-
|
|
|
|
|
|
|
|
|
161 |
# Static files (CSS, JavaScript, Images)
|
162 |
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
163 |
|
@@ -169,3 +176,12 @@ MEDIA_URL = '/media/'
|
|
169 |
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
170 |
|
171 |
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
CORS_ALLOW_ALL_ORIGINS = True # If this is used then `CORS_ALLOWED_ORIGINS` will not have any effect
|
52 |
CORS_ALLOW_CREDENTIALS = True
|
53 |
CORS_ALLOWED_ORIGINS = [
|
54 |
+
'https://undetectable-voice-clone.hf.space', "http://94.101.98.237:7860" ,"https://voice-clone-frontend.vercel.app" ,
|
55 |
+
"https://voice.undetectable.ai" ,"https://api.voice.undetectable.ai"
|
56 |
]
|
57 |
|
58 |
REST_FRAMEWORK = {
|
|
|
123 |
}
|
124 |
}
|
125 |
|
126 |
+
MINIO_ENDPOINT = 'storage.voice.undetectable.ai' # Use the service name defined in Docker Compose
|
127 |
MINIO_ACCESS_KEY = 'voice-clone'
|
128 |
MINIO_SECRET_KEY = 'voice-clone'
|
129 |
+
MINIO_SECURE = True # Change to True if using HTTPS
|
130 |
+
|
131 |
+
AUTH_USER_MODEL = 'accounts.User'
|
132 |
|
133 |
# Password validation
|
134 |
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
|
|
160 |
|
161 |
USE_TZ = True
|
162 |
|
163 |
+
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
164 |
+
ACCOUNT_AUTHENTICATION_METHOD = "email"
|
165 |
+
ACCOUNT_EMAIL_REQUIRED = True
|
166 |
+
ACCOUNT_UNIQUE_EMAIL = True
|
167 |
+
ACCOUNT_USERNAME_REQUIRED = False
|
168 |
# Static files (CSS, JavaScript, Images)
|
169 |
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
170 |
|
|
|
176 |
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
177 |
|
178 |
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
179 |
+
|
180 |
+
|
181 |
+
EMAIL_HOST = "smtp.gmail.com"
|
182 |
+
EMAIL_PORT = 587 # For TLS
|
183 |
+
EMAIL_USE_TLS = True
|
184 |
+
EMAIL_USE_SSL = False
|
185 |
+
EMAIL_HOST_USER='[email protected]'
|
186 |
+
EMAIL_HOST_PASSWORD="fgxx yuns jagn rwak"
|
187 |
+
DEFAULT_FROM_EMAIL="[email protected]"
|
config/urls.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
from django.contrib import admin
|
2 |
from django.urls import path, include
|
|
|
3 |
from rest_framework import permissions
|
4 |
from drf_yasg.views import get_schema_view
|
5 |
from drf_yasg import openapi
|
@@ -17,7 +18,7 @@ schema_view = get_schema_view(
|
|
17 |
license=openapi.License(name="Your License"),
|
18 |
),
|
19 |
public=True,
|
20 |
-
|
21 |
permission_classes=(permissions.AllowAny,),
|
22 |
)
|
23 |
|
|
|
1 |
from django.contrib import admin
|
2 |
from django.urls import path, include
|
3 |
+
from .schemaprotocol import BothHttpAndHttpsSchemaGenerator
|
4 |
from rest_framework import permissions
|
5 |
from drf_yasg.views import get_schema_view
|
6 |
from drf_yasg import openapi
|
|
|
18 |
license=openapi.License(name="Your License"),
|
19 |
),
|
20 |
public=True,
|
21 |
+
# generator_class=BothHttpAndHttpsSchemaGenerator,
|
22 |
permission_classes=(permissions.AllowAny,),
|
23 |
)
|
24 |
|
db.sqlite3
DELETED
Binary file (160 kB)
|
|
project.log
DELETED
The diff for this file is too large to render.
See raw diff
|
|
texttovoice/models.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
|
2 |
from django.db import models
|
3 |
-
from
|
4 |
|
5 |
# Create your models here.
|
6 |
class BaseModel(models.Model):
|
|
|
1 |
|
2 |
from django.db import models
|
3 |
+
from accounts.models import User
|
4 |
|
5 |
# Create your models here.
|
6 |
class BaseModel(models.Model):
|
texttovoice/serializers.py
CHANGED
@@ -8,7 +8,7 @@ class TextToSpeechSerializer(serializers.Serializer):
|
|
8 |
|
9 |
|
10 |
class TextToSpeechSerializerResponse(serializers.ModelSerializer):
|
11 |
-
created_by = serializers.SerializerMethodField()
|
12 |
|
13 |
class Meta:
|
14 |
model = TextToSpeech
|
@@ -16,12 +16,12 @@ class TextToSpeechSerializerResponse(serializers.ModelSerializer):
|
|
16 |
# Ensure that all the fields you want to include are listed here
|
17 |
|
18 |
def get_created_by(self, obj):
|
19 |
-
|
20 |
|
21 |
class TextToSpeechSerializerResponseWithURL(serializers.ModelSerializer):
|
22 |
speaker_wav_url = serializers.SerializerMethodField()
|
23 |
output_wav_url = serializers.SerializerMethodField()
|
24 |
-
created_by = serializers.SerializerMethodField()
|
25 |
|
26 |
|
27 |
class Meta:
|
@@ -36,4 +36,4 @@ class TextToSpeechSerializerResponseWithURL(serializers.ModelSerializer):
|
|
36 |
return self.context['view'].generate_presigned_url(obj.output_wav)
|
37 |
|
38 |
def get_created_by(self, obj):
|
39 |
-
return obj.created_by.
|
|
|
8 |
|
9 |
|
10 |
class TextToSpeechSerializerResponse(serializers.ModelSerializer):
|
11 |
+
#created_by = serializers.SerializerMethodField()
|
12 |
|
13 |
class Meta:
|
14 |
model = TextToSpeech
|
|
|
16 |
# Ensure that all the fields you want to include are listed here
|
17 |
|
18 |
def get_created_by(self, obj):
|
19 |
+
return obj.created_by.email if obj.created_by else None
|
20 |
|
21 |
class TextToSpeechSerializerResponseWithURL(serializers.ModelSerializer):
|
22 |
speaker_wav_url = serializers.SerializerMethodField()
|
23 |
output_wav_url = serializers.SerializerMethodField()
|
24 |
+
#created_by = serializers.SerializerMethodField()
|
25 |
|
26 |
|
27 |
class Meta:
|
|
|
36 |
return self.context['view'].generate_presigned_url(obj.output_wav)
|
37 |
|
38 |
def get_created_by(self, obj):
|
39 |
+
return obj.created_by.email if obj.created_by else None
|