init
This commit is contained in:
122
backend/users/admin.py
Normal file
122
backend/users/admin.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from simplemde.widgets import SimpleMDEEditor
|
||||
|
||||
from users.models import User, University, Major
|
||||
from users.resources import UserResource
|
||||
from users.tasks import send_verification_email
|
||||
from utils.admin import SoftDeleteListFilter, BaseModelAdmin
|
||||
|
||||
|
||||
class UserAdminForm(forms.ModelForm):
|
||||
bio = forms.CharField(widget=SimpleMDEEditor(), required=False)
|
||||
student_id = forms.CharField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = '__all__'
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin, BaseModelAdmin, ImportExportModelAdmin):
|
||||
form = UserAdminForm
|
||||
resource_class = UserResource
|
||||
list_display = ('email', 'username', 'university', 'is_email_verified', 'date_joined')
|
||||
list_filter = ('is_email_verified', 'is_staff', 'year_of_study', SoftDeleteListFilter)
|
||||
search_fields = ('email', 'username', 'student_id', 'first_name', 'last_name')
|
||||
ordering = ('-date_joined',)
|
||||
|
||||
fieldsets = (
|
||||
('Auth Credentials', {'fields': ('username', 'email', 'password')}),
|
||||
('Personal info', {
|
||||
'fields': ('first_name', 'last_name', 'student_id', 'university', 'year_of_study', 'major', 'bio', 'profile_picture')
|
||||
}),
|
||||
('Permissions', {
|
||||
'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions',),
|
||||
}),
|
||||
('Important dates', {'fields': ('last_login', 'date_joined')}),
|
||||
|
||||
('Email Verification', {
|
||||
'fields': ('is_email_verified', 'email_verification_token', 'email_verification_sent_at')
|
||||
}),
|
||||
('Password Reset', {
|
||||
'fields': ('password_reset_token', 'password_reset_token_expires_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Soft Delete', {
|
||||
'fields': ('is_deleted', 'deleted_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
'Step 1',
|
||||
{
|
||||
'classes': ('wide',),
|
||||
'fields': ('email', 'student_id', 'password1', 'password2', 'usable_password'),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
readonly_fields = ('email_verification_token', 'email_verification_sent_at', 'deleted_at',
|
||||
'password_reset_token', 'password_reset_token_expires_at')
|
||||
|
||||
actions = BaseModelAdmin.actions + [
|
||||
'verify_emails',
|
||||
'resend_verification_email',
|
||||
]
|
||||
|
||||
@admin.action(description='Verify selected user emails')
|
||||
def verify_emails(self, request, queryset):
|
||||
queryset.update(is_email_verified=True)
|
||||
self.message_user(request, f'Verified {queryset.count()} user emails.')
|
||||
|
||||
@admin.action(description="Resend verification email")
|
||||
def resend_verification_email(self, request, queryset):
|
||||
qs = queryset.filter(is_email_verified=False).exclude(email__isnull=True).exclude(email="")
|
||||
|
||||
total = queryset.count()
|
||||
to_send = qs.count()
|
||||
skipped = total - to_send
|
||||
sent = failed = 0
|
||||
|
||||
for user in qs:
|
||||
try:
|
||||
user.regenerate_verification_token()
|
||||
user.email_verification_sent_at = timezone.now()
|
||||
user.save(update_fields=["email_verification_sent_at"])
|
||||
|
||||
verification_url = f"{settings.FRONTEND_ROOT}verify-email/{user.email_verification_token}"
|
||||
send_verification_email.delay(user.id, verification_url)
|
||||
sent += 1
|
||||
except Exception as exc:
|
||||
failed += 1
|
||||
|
||||
if sent:
|
||||
self.message_user(request, f"ایمیل تأیید برای {sent} کاربر ارسال شد.", level=messages.SUCCESS)
|
||||
if skipped:
|
||||
self.message_user(
|
||||
request,
|
||||
f"{skipped} کاربر کنار گذاشته شدند (یا قبلاً تأیید شدهاند یا ایمیل ندارند).",
|
||||
level=messages.WARNING,
|
||||
)
|
||||
if failed:
|
||||
self.message_user(request, f"ارسال برای {failed} کاربر با خطا مواجه شد.", level=messages.ERROR)
|
||||
|
||||
|
||||
@admin.register(University)
|
||||
class UniversityAdmin(BaseModelAdmin):
|
||||
list_display = ('name', 'code', 'is_active', 'created_at')
|
||||
list_filter = ('is_active', SoftDeleteListFilter)
|
||||
search_fields = ('name', 'code')
|
||||
|
||||
|
||||
@admin.register(Major)
|
||||
class MajorAdmin(BaseModelAdmin):
|
||||
list_display = ('name', 'code', 'is_active', 'created_at')
|
||||
list_filter = ('is_active', SoftDeleteListFilter)
|
||||
search_fields = ('name', 'code')
|
||||
8
backend/users/apps.py
Normal file
8
backend/users/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'users'
|
||||
|
||||
def ready(self):
|
||||
import users.signals
|
||||
48
backend/users/fixtures/agile.json
Normal file
48
backend/users/fixtures/agile.json
Normal file
@@ -0,0 +1,48 @@
|
||||
[
|
||||
{"model":"users.user","fields":{"username":"u1403020111029","email":"pending-1403020111029@noemail.local","first_name":"پوریا","last_name":"شامخی","student_id":"1403020111029","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1400020111002","email":"pending-1400020111002@noemail.local","first_name":"سمانه","last_name":"جباری","student_id":"1400020111002","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201110035","email":"pending-990201110035@noemail.local","first_name":"سید علی","last_name":"حجتی مقدم","student_id":"990201110035","year_of_study":1399,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201200032","email":"pending-990201200032@noemail.local","first_name":"مهدی","last_name":"خدیوی سرشت","student_id":"990201200032","year_of_study":1399,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020111026","email":"pending-1403020111026@noemail.local","first_name":"امیر سجاد","last_name":"حیدری","student_id":"1403020111026","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020111037","email":"pending-1403020111037@noemail.local","first_name":"امیرکیان","last_name":"رادپور","student_id":"1403020111037","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120011","email":"pending-1401020120011@noemail.local","first_name":"شیما","last_name":"گندمکار","student_id":"1401020120011","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120024","email":"pending-1401020120024@noemail.local","first_name":"رضا","last_name":"سالمیدرگاهی","student_id":"1401020120024","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120102","email":"pending-1401020120102@noemail.local","first_name":"امیرمحمد","last_name":"نیککار","student_id":"1401020120102","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120028","email":"pending-1401020120028@noemail.local","first_name":"امیرمحمد","last_name":"کیانفر","student_id":"1401020120028","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120035","email":"pending-1401020120035@noemail.local","first_name":"رژان","last_name":"پناهیپور","student_id":"1401020120035","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1400020111032","email":"pending-1400020111032@noemail.local","first_name":"مریم","last_name":"صفری","student_id":"1400020111032","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1400020111014","email":"pending-1400020111014@noemail.local","first_name":"علیرضا","last_name":"رحیمی","student_id":"1400020111014","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u992818200","email":"pending-992818200@noemail.local","first_name":"مریم","last_name":"مسلمی دوران محله","student_id":"992818200","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1400020111022","email":"pending-1400020111022@noemail.local","first_name":"امیرمحمد","last_name":"خیراندیش","student_id":"1400020111022","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1400020111029","email":"pending-1400020111029@noemail.local","first_name":"امیرحسین","last_name":"حسنپور","student_id":"1400020111029","year_of_study":1400,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201201007","email":"pending-990201201007@noemail.local","first_name":"امیررضا","last_name":"اخلاقی","student_id":"990201201007","year_of_study":1399,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020111006","email":"pending-1403020111006@noemail.local","first_name":"سینا","last_name":"زمانپور","student_id":"1403020111006","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020130021","email":"pending-1403020130021@noemail.local","first_name":"سبحان","last_name":"آسوده جلالی","student_id":"1403020130021","year_of_study":1403,"major":null,"university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403012268121","email":"pending-1403012268121@noemail.local","first_name":"فربد","last_name":"خلیلی خوشه مهر","student_id":"1403012268121","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u03111129302057","email":"pending-03111129302057@noemail.local","first_name":"محمد مهدی","last_name":"جباری","student_id":"03111129302057","year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121009","email":"pending-1403020121009@noemail.local","first_name":"امیرحسین","last_name":"امینپور","student_id":"1403020121009","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121013","email":"pending-1403020121013@noemail.local","first_name":"عرشیا","last_name":"عرشی","student_id":"1403020121013","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121023","email":"pending-1403020121023@noemail.local","first_name":"طاها","last_name":"محیط مافی","student_id":"1403020121023","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"uidx28","email":"pending-idx28@noemail.local","first_name":"مهدی","last_name":"منصورپور","student_id":null,"year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121007","email":"pending-1403020121007@noemail.local","first_name":"سید محمدرضا","last_name":"حسیننیان","student_id":"1403020121007","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121001","email":"pending-1403020121001@noemail.local","first_name":"محمود","last_name":"یاسری","student_id":"1403020121001","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120039","email":"pending-1401020120039@noemail.local","first_name":"ارشاد","last_name":"ایزدی","student_id":"1401020120039","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120002","email":"pending-1401020120002@noemail.local","first_name":"دلناز","last_name":"محمودی","student_id":"1401020120002","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121018","email":"pending-1403020121018@noemail.local","first_name":"اروین","last_name":"نعمتی","student_id":"1403020121018","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120149","email":"pending-1401020120149@noemail.local","first_name":"مائده","last_name":"حسرت قرانی","student_id":"1401020120149","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120036","email":"pending-1401020120036@noemail.local","first_name":"شهریار","last_name":"اقاجانی","student_id":"1401020120036","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121027","email":"pending-1403020121027@noemail.local","first_name":"عمید","last_name":"عباسی","student_id":"1403020121027","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201200016","email":"pending-990201200016@noemail.local","first_name":"مهدی","last_name":"دیداری","student_id":"990201200016","year_of_study":1399,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120041","email":"pending-1401020120041@noemail.local","first_name":"حمید","last_name":"عباسی","student_id":"1401020120041","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020130022","email":"pending-1403020130022@noemail.local","first_name":"امیرمحمد","last_name":"نجفی","student_id":"1403020130022","year_of_study":1403,"major":null,"university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020111049","email":"pending-1401020111049@noemail.local","first_name":"علی","last_name":"رهگذر","student_id":"1401020111049","year_of_study":1401,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120103","email":"pending-1401020120103@noemail.local","first_name":"یاسان","last_name":"حاجقلیزاده","student_id":"1401020120103","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"uidx45","email":"pending-idx45@noemail.local","first_name":"امیر","last_name":"دوستی ماسوله","student_id":null,"year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120031","email":"pending-1401020120031@noemail.local","first_name":"امیررضا","last_name":"علیپور","student_id":"1401020120031","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201200036","email":"pending-990201200036@noemail.local","first_name":"مونا","last_name":"یحییزاده واقفی","student_id":"990201200036","year_of_study":1399,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120005","email":"pending-1401020120005@noemail.local","first_name":"بهار","last_name":"محمدی","student_id":"1401020120005","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1401020120026","email":"pending-1401020120026@noemail.local","first_name":"مطهره","last_name":"حقشناس","student_id":"1401020120026","year_of_study":1401,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u1403020121020","email":"pending-1403020121020@noemail.local","first_name":"محمد","last_name":"خلیلیمقدم ملامحله","student_id":"1403020121020","year_of_study":1403,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"u990201200027","email":"pending-990201200027@noemail.local","first_name":"مهراب","last_name":"گودرزی","student_id":"990201200027","year_of_study":1399,"major":"IE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}},
|
||||
{"model":"users.user","fields":{"username":"uidx52","email":"pending-idx52@noemail.local","first_name":"امیرمحمد","last_name":"چرختاب مقدم","student_id":null,"year_of_study":1403,"major":"CE","university":"GILAN","is_email_verified":false,"password":"pbkdf2_sha256$390000$guilan$2FFrsLkTODb20Jf7oxtxgXXL1z6uQY8iCJJ2OGANkKk=","is_active":true,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}}
|
||||
]
|
||||
244
backend/users/fixtures/users.json
Normal file
244
backend/users/fixtures/users.json
Normal file
@@ -0,0 +1,244 @@
|
||||
[
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"username": "admin",
|
||||
"email": "admin@cs-association.ac.ir",
|
||||
"first_name": "علی",
|
||||
"last_name": "احمدی",
|
||||
"student_id": "9812345001",
|
||||
"year_of_study": 4,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "رئیس انجمن علمی مهندسی کامپیوتر دانشگاه",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"is_staff": true,
|
||||
"is_superuser": true,
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-01T10:00:00Z",
|
||||
"updated_at": "2024-01-01T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"username": "sara_mohammadi",
|
||||
"email": "sara.mohammadi@student.ac.ir",
|
||||
"first_name": "سارا",
|
||||
"last_name": "محمدی",
|
||||
"student_id": "9912345002",
|
||||
"year_of_study": 3,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "نایب رئیس انجمن و مسئول رویدادها",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-02T10:00:00Z",
|
||||
"updated_at": "2024-01-02T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"username": "reza_karimi",
|
||||
"email": "reza.karimi@student.ac.ir",
|
||||
"first_name": "رضا",
|
||||
"last_name": "کریمی",
|
||||
"student_id": "9912345003",
|
||||
"year_of_study": 2,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "علاقهمند به هوش مصنوعی و یادگیری ماشین",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-03T10:00:00Z",
|
||||
"updated_at": "2024-01-03T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"username": "maryam_hosseini",
|
||||
"email": "maryam.hosseini@student.ac.ir",
|
||||
"first_name": "مریم",
|
||||
"last_name": "حسینی",
|
||||
"student_id": "0012345004",
|
||||
"year_of_study": 1,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "دانشجوی سال اول و علاقهمند به برنامهنویسی وب",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440004",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-04T10:00:00Z",
|
||||
"updated_at": "2024-01-04T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"username": "hassan_zare",
|
||||
"email": "hassan.zare@student.ac.ir",
|
||||
"first_name": "حسن",
|
||||
"last_name": "زارع",
|
||||
"student_id": "9812345005",
|
||||
"year_of_study": 4,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "مسئول روابط عمومی انجمن",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440005",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-05T10:00:00Z",
|
||||
"updated_at": "2024-01-05T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"username": "zahra_safari",
|
||||
"email": "zahra.safari@student.ac.ir",
|
||||
"first_name": "زهرا",
|
||||
"last_name": "صفری",
|
||||
"student_id": "9912345006",
|
||||
"year_of_study": 3,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "علاقهمند به امنیت سایبری و شبکه",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440006",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-06T10:00:00Z",
|
||||
"updated_at": "2024-01-06T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"username": "mohammad_rahmani",
|
||||
"email": "mohammad.rahmani@student.ac.ir",
|
||||
"first_name": "محمد",
|
||||
"last_name": "رحمانی",
|
||||
"student_id": "0012345007",
|
||||
"year_of_study": 1,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "دانشجوی جدید الورود",
|
||||
"is_email_verified": false,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440007",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-07T10:00:00Z",
|
||||
"updated_at": "2024-01-07T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"username": "fateme_moradi",
|
||||
"email": "fateme.moradi@student.ac.ir",
|
||||
"first_name": "فاطمه",
|
||||
"last_name": "مرادی",
|
||||
"student_id": "9912345008",
|
||||
"year_of_study": 2,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "علاقهمند به توسعه اپلیکیشن موبایل",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440008",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-08T10:00:00Z",
|
||||
"updated_at": "2024-01-08T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"username": "amir_ghorbani",
|
||||
"email": "amir.ghorbani@student.ac.ir",
|
||||
"first_name": "امیر",
|
||||
"last_name": "قربانی",
|
||||
"student_id": "9812345009",
|
||||
"year_of_study": 4,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "مسئول فنی انجمن و توسعهدهنده وبسایت",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440009",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-09T10:00:00Z",
|
||||
"updated_at": "2024-01-09T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"username": "nasrin_jafari",
|
||||
"email": "nasrin.jafari@student.ac.ir",
|
||||
"first_name": "نسرین",
|
||||
"last_name": "جعفری",
|
||||
"student_id": "9912345010",
|
||||
"year_of_study": 3,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "علاقهمند به علم داده و تحلیل داده",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440010",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-10T10:00:00Z",
|
||||
"updated_at": "2024-01-10T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"username": "mehdi_bagheri",
|
||||
"email": "mehdi.bagheri@student.ac.ir",
|
||||
"first_name": "مهدی",
|
||||
"last_name": "باقری",
|
||||
"student_id": "0012345011",
|
||||
"year_of_study": 1,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "دانشجوی سال اول و علاقهمند به بازیسازی",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440011",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-11T10:00:00Z",
|
||||
"updated_at": "2024-01-11T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "users.user",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"username": "leila_mousavi",
|
||||
"email": "leila.mousavi@student.ac.ir",
|
||||
"first_name": "لیلا",
|
||||
"last_name": "موسوی",
|
||||
"student_id": "9912345012",
|
||||
"year_of_study": 2,
|
||||
"major": "مهندسی کامپیوتر",
|
||||
"bio": "علاقهمند به طراحی UI/UX",
|
||||
"is_email_verified": true,
|
||||
"email_verification_token": "550e8400-e29b-41d4-a716-446655440012",
|
||||
"password": "pbkdf2_sha256$600000$test$test",
|
||||
"created_at": "2024-01-12T10:00:00Z",
|
||||
"updated_at": "2024-01-12T10:00:00Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
}
|
||||
]
|
||||
60
backend/users/migrations/0001_initial.py
Normal file
60
backend/users/migrations/0001_initial.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 5.2.5 on 2025-10-16 12:07
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
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')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('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')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('bio', models.TextField(blank=True, null=True)),
|
||||
('profile_picture', models.ImageField(blank=True, null=True, upload_to='profile_pictures/')),
|
||||
('student_id', models.CharField(max_length=20, null=True)),
|
||||
('year_of_study', models.IntegerField(blank=True, null=True)),
|
||||
('major', models.CharField(blank=True, choices=[('CE', 'مهندسی کامپیوتر'), ('CS', 'علوم کامپیوتر'), ('SE', 'مهندسی نرم\u200cافزار'), ('IT', 'فناوری اطلاعات'), ('AI', 'هوش مصنوعی و رباتیک'), ('DATA', 'علم داده'), ('EE', 'مهندسی برق'), ('ME', 'مهندسی مکانیک'), ('CIV', 'مهندسی عمران'), ('CHE', 'مهندسی شیمی'), ('IE', 'مهندسی صنایع'), ('MSE', 'مهندسی مواد و متالورژی'), ('BME', 'مهندسی پزشکی'), ('ARCH', 'معماری'), ('AERO', 'مهندسی هوافضا'), ('PET', 'مهندسی نفت'), ('MIN', 'مهندسی معدن'), ('ENV', 'مهندسی محیط\u200cزیست'), ('URP', 'برنامه\u200cریزی شهری و منطقه\u200cای'), ('MATH', 'ریاضیات'), ('STAT', 'آمار'), ('PHYS', 'فیزیک'), ('CHEM', 'شیمی'), ('BIO', 'زیست\u200cشناسی'), ('GEO', 'زمین\u200cشناسی'), ('MED', 'پزشکی'), ('DEN', 'دندان\u200cپزشکی'), ('PHARM', 'داروسازی'), ('NURS', 'پرستاری'), ('MID', 'مامایی'), ('LAB', 'علوم آزمایشگاهی'), ('RAD', 'رادیولوژی'), ('ANES', 'بیهوشی'), ('PUBH', 'بهداشت'), ('AGRI', 'کشاورزی (عمومی)'), ('HORT', 'باغبانی'), ('PLP', 'گیاه\u200cپزشکی'), ('SOIL', 'علوم خاک'), ('VET', 'دامپزشکی'), ('MGT', 'مدیریت'), ('ACC', 'حسابداری'), ('FIN', 'مالی'), ('ECO', 'اقتصاد'), ('BA', 'مدیریت بازرگانی'), ('LAW', 'حقوق'), ('POL', 'علوم سیاسی'), ('SOC', 'جامعه\u200cشناسی'), ('PSY', 'روان\u200cشناسی'), ('PHIL', 'فلسفه'), ('HIST', 'تاریخ'), ('GEOG', 'جغرافیا'), ('EDU', 'علوم تربیتی'), ('PEd', 'تربیت بدنی'), ('LIT_FA', 'زبان و ادبیات فارسی'), ('LIT_EN', 'زبان و ادبیات انگلیسی'), ('LIT_AR', 'زبان و ادبیات عربی'), ('TRAN_EN', 'مترجمی زبان انگلیسی'), ('ART', 'هنرهای تجسمی'), ('GRAPH', 'گرافیک'), ('MUSIC', 'موسیقی'), ('THEAT', 'نمایش و تئاتر')], max_length=16, null=True)),
|
||||
('university', models.CharField(blank=True, choices=[('UT', 'دانشگاه تهران'), ('AUT', 'دانشگاه صنعتی امیرکبیر'), ('SHARIF', 'دانشگاه صنعتی شریف'), ('SBU', 'دانشگاه شهید بهشتی'), ('IUST', 'دانشگاه علم و صنعت ایران'), ('KNTU', 'دانشگاه صنعتی خواجه\u200cنصیر'), ('MODARES', 'دانشگاه تربیت مدرس'), ('ALLAMEH', 'دانشگاه علامه طباطبایی'), ('KHARAZMI', 'دانشگاه خوارزمی'), ('ISFAHAN_UNI', 'دانشگاه اصفهان'), ('IUT', 'دانشگاه صنعتی اصفهان'), ('SHIRAZ_UNI', 'دانشگاه شیراز'), ('TABRIZ_UNI', 'دانشگاه تبریز'), ('FERDOWSI', 'دانشگاه فردوسی مشهد'), ('RAZI', 'دانشگاه رازی'), ('BUALI', 'دانشگاه بوعلی\u200cسینا'), ('KURDISTAN', 'دانشگاه کردستان'), ('YAZD_UNI', 'دانشگاه یزد'), ('KERMAN_UNI', 'دانشگاه شهید باهنر کرمان'), ('MAZANDARAN', 'دانشگاه مازندران'), ('GILAN', 'دانشگاه گیلان'), ('GOLESTAN', 'دانشگاه گلستان'), ('URMIA', 'دانشگاه ارومیه'), ('ZANJAN', 'دانشگاه زنجان'), ('ARDABIL', 'دانشگاه محقق اردبیلی'), ('ARAK_UNI', 'دانشگاه اراک'), ('SEMNAN', 'دانشگاه سمنان'), ('SHAHROOD', 'دانشگاه صنعتی شاهرود'), ('QOM_UNI', 'دانشگاه قم'), ('IKIU', 'دانشگاه بین\u200cالمللی امام خمینی قزوین'), ('MAL_ASHTAR', 'دانشگاه صنعتی مالک\u200cاشتر'), ('SAHAND', 'دانشگاه صنعتی سهند'), ('BABOL_NOSH', 'دانشگاه صنعتی نوشیروانی بابل'), ('TOLOU', 'دانشگاه تحصیلات تکمیلی صنعتی و فناوری پیشرفته کرمان'), ('TUMS', 'دانشگاه علوم پزشکی تهران'), ('SBMU_MED', 'دانشگاه علوم پزشکی شهید بهشتی'), ('IUMS_MED', 'دانشگاه علوم پزشکی ایران'), ('MUMS_MED', 'دانشگاه علوم پزشکی مشهد'), ('SUMS_MED', 'دانشگاه علوم پزشکی شیراز'), ('TBZ_MED', 'دانشگاه علوم پزشکی تبریز'), ('ISF_MED', 'دانشگاه علوم پزشکی اصفهان'), ('AJA_MED', 'دانشگاه علوم پزشکی ارتش'), ('AJUMS_MED', 'دانشگاه علوم پزشکی اهواز'), ('KUMS_MED', 'دانشگاه علوم پزشکی کرمانشاه'), ('KER_MED', 'دانشگاه علوم پزشکی کرمان'), ('IAU_TEH', 'دانشگاه آزاد اسلامی تهران'), ('IAU_SCIRES', 'دانشگاه آزاد اسلامی علوم و تحقیقات تهران'), ('IAU_MASH', 'دانشگاه آزاد اسلامی مشهد'), ('IAU_TBRZ', 'دانشگاه آزاد اسلامی تبریز'), ('IAU_SHIR', 'دانشگاه آزاد اسلامی شیراز'), ('IAU_ISF', 'دانشگاه آزاد اسلامی اصفهان (خوراسگان)'), ('IAU_RASHT', 'دانشگاه آزاد اسلامی رشت'), ('IAU_QAZ', 'دانشگاه آزاد اسلامی قزوین'), ('PNU_TEH', 'دانشگاه پیام نور تهران'), ('PNU_RAS', 'دانشگاه پیام نور رشت'), ('PNU_MASH', 'دانشگاه پیام نور مشهد'), ('PNU_TBRZ', 'دانشگاه پیام نور تبریز'), ('UAST_TEH', 'دانشگاه علمی-کاربردی تهران'), ('UAST_GIL', 'دانشگاه علمی-کاربردی گیلان'), ('TVU_TEH', 'دانشگاه فنی و حرفه\u200cای تهران'), ('TVU_GIL', 'دانشگاه فنی و حرفه\u200cای گیلان'), ('RAJAEI', 'دانشگاه تربیت دبیر شهید رجایی'), ('IMAM_SADEQ', 'دانشگاه امام صادق (ع)'), ('ART_TEH', 'دانشگاه هنر'), ('TEH_MARK', 'دانشگاه علامه محدث نوری/علامه طباطبایی (در صورت نیاز اصلاح کنید)')], max_length=16, null=True)),
|
||||
('is_email_verified', models.BooleanField(default=False)),
|
||||
('email_verification_token', models.UUIDField(default=uuid.uuid4, unique=True)),
|
||||
('email_verification_sent_at', models.DateTimeField(blank=True, null=True)),
|
||||
('password_reset_token', models.UUIDField(blank=True, null=True, unique=True)),
|
||||
('password_reset_token_expires_at', models.DateTimeField(blank=True, null=True)),
|
||||
('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',
|
||||
'db_table': 'users',
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
backend/users/migrations/0002_alter_user_university.py
Normal file
18
backend/users/migrations/0002_alter_user_university.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.5 on 2025-10-18 10:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='university',
|
||||
field=models.CharField(blank=True, choices=[('UT', 'دانشگاه تهران'), ('AUT', 'دانشگاه صنعتی امیرکبیر'), ('SHARIF', 'دانشگاه صنعتی شریف'), ('SBU', 'دانشگاه شهید بهشتی'), ('IUST', 'دانشگاه علم و صنعت ایران'), ('KNTU', 'دانشگاه صنعتی خواجه\u200cنصیر'), ('MODARES', 'دانشگاه تربیت مدرس'), ('ALLAMEH', 'دانشگاه علامه طباطبایی'), ('KHARAZMI', 'دانشگاه خوارزمی'), ('ISFAHAN_UNI', 'دانشگاه اصفهان'), ('IUT', 'دانشگاه صنعتی اصفهان'), ('SHIRAZ_UNI', 'دانشگاه شیراز'), ('TABRIZ_UNI', 'دانشگاه تبریز'), ('FERDOWSI', 'دانشگاه فردوسی مشهد'), ('RAZI', 'دانشگاه رازی'), ('BUALI', 'دانشگاه بوعلی\u200cسینا'), ('KURDISTAN', 'دانشگاه کردستان'), ('YAZD_UNI', 'دانشگاه یزد'), ('KERMAN_UNI', 'دانشگاه شهید باهنر کرمان'), ('MAZANDARAN', 'دانشگاه مازندران'), ('GILAN', 'دانشگاه گیلان'), ('GOLESTAN', 'دانشگاه گلستان'), ('URMIA', 'دانشگاه ارومیه'), ('ZANJAN', 'دانشگاه زنجان'), ('ARDABIL', 'دانشگاه محقق اردبیلی'), ('ARAK_UNI', 'دانشگاه اراک'), ('SEMNAN', 'دانشگاه سمنان'), ('SHAHROOD', 'دانشگاه صنعتی شاهرود'), ('QOM_UNI', 'دانشگاه قم'), ('IKIU', 'دانشگاه بین\u200cالمللی امام خمینی قزوین'), ('MAL_ASHTAR', 'دانشگاه صنعتی مالک\u200cاشتر'), ('SAHAND', 'دانشگاه صنعتی سهند'), ('BABOL_NOSH', 'دانشگاه صنعتی نوشیروانی بابل'), ('TOLOU', 'دانشگاه تحصیلات تکمیلی صنعتی و فناوری پیشرفته کرمان'), ('TUMS', 'دانشگاه علوم پزشکی تهران'), ('SBMU_MED', 'دانشگاه علوم پزشکی شهید بهشتی'), ('IUMS_MED', 'دانشگاه علوم پزشکی ایران'), ('MUMS_MED', 'دانشگاه علوم پزشکی مشهد'), ('SUMS_MED', 'دانشگاه علوم پزشکی شیراز'), ('TBZ_MED', 'دانشگاه علوم پزشکی تبریز'), ('ISF_MED', 'دانشگاه علوم پزشکی اصفهان'), ('AJA_MED', 'دانشگاه علوم پزشکی ارتش'), ('AJUMS_MED', 'دانشگاه علوم پزشکی اهواز'), ('KUMS_MED', 'دانشگاه علوم پزشکی کرمانشاه'), ('KER_MED', 'دانشگاه علوم پزشکی کرمان'), ('IAU_TEH', 'دانشگاه آزاد اسلامی تهران'), ('IAU_SCIRES', 'دانشگاه آزاد اسلامی علوم و تحقیقات تهران'), ('IAU_MASH', 'دانشگاه آزاد اسلامی مشهد'), ('IAU_TBRZ', 'دانشگاه آزاد اسلامی تبریز'), ('IAU_SHIR', 'دانشگاه آزاد اسلامی شیراز'), ('IAU_ISF', 'دانشگاه آزاد اسلامی اصفهان'), ('IAU_RASHT', 'دانشگاه آزاد اسلامی رشت'), ('IAU_LAHIJAN', 'دانشگاه آزاد اسلامی لاهیجان'), ('IAU_QAZ', 'دانشگاه آزاد اسلامی قزوین'), ('PNU_TEH', 'دانشگاه پیام نور تهران'), ('PNU_RAS', 'دانشگاه پیام نور رشت'), ('PNU_MASH', 'دانشگاه پیام نور مشهد'), ('PNU_TBRZ', 'دانشگاه پیام نور تبریز'), ('UAST_TEH', 'دانشگاه علمی-کاربردی تهران'), ('UAST_GIL', 'دانشگاه علمی-کاربردی گیلان'), ('TVU_TEH', 'دانشگاه فنی و حرفه\u200cای تهران'), ('TVU_GIL', 'دانشگاه فنی و حرفه\u200cای گیلان'), ('RAJAEI', 'دانشگاه تربیت دبیر شهید رجایی'), ('IMAM_SADEQ', 'دانشگاه امام صادق (ع)'), ('ART_TEH', 'دانشگاه هنر')], max_length=127, null=True),
|
||||
),
|
||||
]
|
||||
18
backend/users/migrations/0003_alter_user_university.py
Normal file
18
backend/users/migrations/0003_alter_user_university.py
Normal file
File diff suppressed because one or more lines are too long
116
backend/users/migrations/0004_major_university_models.py
Normal file
116
backend/users/migrations/0004_major_university_models.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
from utils.choices import MajorChoices, UniversityChoices
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0003_alter_user_university"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="University",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("is_deleted", models.BooleanField(default=False)),
|
||||
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
||||
("code", models.CharField(max_length=64, unique=True)),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Major",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("is_deleted", models.BooleanField(default=False)),
|
||||
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
||||
("code", models.CharField(max_length=64, unique=True)),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
"ordering": ["name"],
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="major",
|
||||
new_name="legacy_major",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="user",
|
||||
old_name="university",
|
||||
new_name="legacy_university",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="legacy_major",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=MajorChoices.choices,
|
||||
editable=False,
|
||||
max_length=16,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="legacy_university",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=UniversityChoices.choices,
|
||||
editable=False,
|
||||
max_length=127,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="major",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="users",
|
||||
to="users.major",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="university",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="users",
|
||||
to="users.university",
|
||||
),
|
||||
),
|
||||
]
|
||||
60
backend/users/migrations/0005_populate_major_university.py
Normal file
60
backend/users/migrations/0005_populate_major_university.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from django.db import migrations
|
||||
|
||||
from utils.choices import MajorChoices, UniversityChoices
|
||||
|
||||
|
||||
def seed_reference_models(apps, schema_editor):
|
||||
Major = apps.get_model("users", "Major")
|
||||
University = apps.get_model("users", "University")
|
||||
User = apps.get_model("users", "User")
|
||||
|
||||
major_map = {}
|
||||
for code, label in MajorChoices.choices:
|
||||
obj, _ = Major.objects.update_or_create(
|
||||
code=code,
|
||||
defaults={"name": label},
|
||||
)
|
||||
major_map[code] = obj
|
||||
|
||||
university_map = {}
|
||||
for code, label in UniversityChoices.choices:
|
||||
obj, _ = University.objects.update_or_create(
|
||||
code=code,
|
||||
defaults={"name": label},
|
||||
)
|
||||
university_map[code] = obj
|
||||
|
||||
users = User.objects.all()
|
||||
for user in users.iterator():
|
||||
updates = []
|
||||
major_code = getattr(user, "legacy_major", None)
|
||||
if major_code:
|
||||
major = major_map.get(major_code)
|
||||
if major and user.major_id != major.id:
|
||||
user.major_id = major.id
|
||||
updates.append("major")
|
||||
|
||||
university_code = getattr(user, "legacy_university", None)
|
||||
if university_code:
|
||||
uni = university_map.get(university_code)
|
||||
if uni and user.university_id != uni.id:
|
||||
user.university_id = uni.id
|
||||
updates.append("university")
|
||||
|
||||
if updates:
|
||||
user.save(update_fields=updates)
|
||||
|
||||
|
||||
def noop(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0004_major_university_models"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(seed_reference_models, noop),
|
||||
]
|
||||
19
backend/users/migrations/0006_remove_legacy_fields.py
Normal file
19
backend/users/migrations/0006_remove_legacy_fields.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0005_populate_major_university"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="legacy_major",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="user",
|
||||
name="legacy_university",
|
||||
),
|
||||
]
|
||||
0
backend/users/migrations/__init__.py
Normal file
0
backend/users/migrations/__init__.py
Normal file
112
backend/users/models.py
Normal file
112
backend/users/models.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.utils import timezone
|
||||
from django.db import models
|
||||
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
||||
from utils.models import BaseModel
|
||||
|
||||
|
||||
class University(BaseModel):
|
||||
code = models.CharField(max_length=64, unique=True)
|
||||
name = models.CharField(max_length=255)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Major(BaseModel):
|
||||
code = models.CharField(max_length=64, unique=True)
|
||||
name = models.CharField(max_length=255)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class User(AbstractUser, BaseModel):
|
||||
email = models.EmailField(unique=True)
|
||||
bio = models.TextField(null=True, blank=True)
|
||||
profile_picture = models.ImageField(upload_to='profile_pictures/', null=True, blank=True)
|
||||
|
||||
student_id = models.CharField(max_length=20, null=True)
|
||||
year_of_study = models.IntegerField(null=True, blank=True)
|
||||
major = models.ForeignKey(
|
||||
Major,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='users',
|
||||
)
|
||||
university = models.ForeignKey(
|
||||
University,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='users',
|
||||
)
|
||||
is_email_verified = models.BooleanField(default=False)
|
||||
email_verification_token = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||
email_verification_sent_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
password_reset_token = models.UUIDField(null=True, blank=True, unique=True)
|
||||
password_reset_token_expires_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ['username']
|
||||
|
||||
class Meta:
|
||||
db_table = 'users'
|
||||
verbose_name = 'User'
|
||||
verbose_name_plural = 'Users'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_full_name()} ({self.email})"
|
||||
|
||||
def get_full_name(self):
|
||||
return f"{self.first_name} {self.last_name}".strip()
|
||||
|
||||
def get_major_display(self):
|
||||
if self.major:
|
||||
return self.major.name
|
||||
return None
|
||||
|
||||
def get_university_display(self):
|
||||
if self.university:
|
||||
return self.university.name
|
||||
return None
|
||||
|
||||
def regenerate_verification_token(self):
|
||||
self.email_verification_token = uuid.uuid4()
|
||||
self.save(update_fields=['email_verification_token'])
|
||||
|
||||
def set_password_reset_token(self):
|
||||
"""Generates a new password reset token and sets its expiry."""
|
||||
self.password_reset_token = uuid.uuid4()
|
||||
self.password_reset_token_expires_at = timezone.now() + timedelta(hours=1)
|
||||
self.save(update_fields=['password_reset_token', 'password_reset_token_expires_at'])
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
send_verified_success = False
|
||||
|
||||
if self.pk is not None:
|
||||
prev = type(self).objects.filter(pk=self.pk).values_list('is_email_verified', flat=True).first()
|
||||
if prev is not None and prev is False and self.is_email_verified is True:
|
||||
send_verified_success = True
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if send_verified_success:
|
||||
try:
|
||||
from users.tasks import send_email_verified_success
|
||||
send_email_verified_success.delay(self.id)
|
||||
except Exception:
|
||||
pass
|
||||
29
backend/users/resources.py
Normal file
29
backend/users/resources.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from import_export import resources, fields
|
||||
from import_export.widgets import BooleanWidget
|
||||
|
||||
from users.models import User
|
||||
|
||||
class UserResource(resources.ModelResource):
|
||||
is_staff = fields.Field(
|
||||
column_name='is_staff',
|
||||
attribute='is_staff',
|
||||
widget=BooleanWidget()
|
||||
)
|
||||
is_superuser = fields.Field(
|
||||
column_name='is_superuser',
|
||||
attribute='is_superuser',
|
||||
widget=BooleanWidget()
|
||||
)
|
||||
is_email_verified = fields.Field(
|
||||
column_name='is_email_verified',
|
||||
attribute='is_email_verified',
|
||||
widget=BooleanWidget()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'email', 'first_name', 'last_name',
|
||||
'student_id', 'year_of_study', 'major',
|
||||
'is_staff', 'is_superuser',
|
||||
'is_email_verified', 'bio')
|
||||
export_order = fields
|
||||
27
backend/users/signals.py
Normal file
27
backend/users/signals.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import uuid
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from users.models import User
|
||||
from users.tasks import send_verification_email
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def send_verification_email_on_registration(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
if not instance.username:
|
||||
instance.username = str(uuid.uuid4())[:10]
|
||||
instance.save(update_fields=['username'])
|
||||
|
||||
if not instance.is_email_verified and instance.email:
|
||||
# Update the email verification sent timestamp
|
||||
instance.email_verification_sent_at = timezone.now()
|
||||
instance.save(update_fields=['email_verification_sent_at'])
|
||||
|
||||
# Generate verification URL (you'll need to adjust this based on your frontend)
|
||||
verification_url = f"{settings.FRONTEND_ROOT}verify-email/{instance.email_verification_token}"
|
||||
|
||||
# Send verification email asynchronously
|
||||
send_verification_email.delay(instance.id, verification_url)
|
||||
99
backend/users/tasks.py
Normal file
99
backend/users/tasks.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from django.core.mail import send_mail
|
||||
from django.template.loader import render_to_string
|
||||
from django.conf import settings
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
from celery import shared_task
|
||||
import logging
|
||||
|
||||
from users.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def send_verification_email(self, user_id, verification_url):
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
|
||||
subject = 'تایید ایمیل | انجمن علمی مهندسی کامپیوتر'
|
||||
html_message = render_to_string('emails/verification_email.html', {
|
||||
'user': user,
|
||||
'verification_url': verification_url,
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[user.email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
logger.info(f"Verification email sent to {user.email}")
|
||||
return f"Verification email sent to {user.email}"
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to send verification email: {exc}")
|
||||
raise self.retry(exc=exc, countdown=60)
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def send_password_reset_email(self, user_id, reset_url):
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
|
||||
subject = 'بازیابی رمز عبور | انجمن علمی مهندسی کامپیوتر'
|
||||
html_message = render_to_string('emails/password_reset_email.html', {
|
||||
'user': user,
|
||||
'reset_url': reset_url,
|
||||
})
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[user.email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
logger.info(f"Password reset email sent to {user.email}")
|
||||
return f"Password reset email sent to {user.email}"
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to send password reset email: {exc}")
|
||||
raise self.retry(exc=exc, countdown=60)
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def send_email_verified_success(self, user_id: int):
|
||||
"""
|
||||
ارسال ایمیل «ایمیل شما با موفقیت تأیید شد» پس از تغییر وضعیت تأیید.
|
||||
"""
|
||||
try:
|
||||
user = User.objects.get(pk=user_id)
|
||||
|
||||
subject = "تأیید ایمیل شما با موفقیت انجام شد"
|
||||
context = {
|
||||
"user": user,
|
||||
"home_url": getattr(settings, "FRONTEND_ROOT", "/"),
|
||||
}
|
||||
html_message = render_to_string("emails/verification_success.html", context)
|
||||
plain_message = strip_tags(html_message)
|
||||
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=plain_message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[user.email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
logger.info(f"verified success email sent to {user.email}")
|
||||
return f"verified success email sent to {user.email}"
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(f"Failed to send verified success email: {exc}")
|
||||
raise self.retry(exc=exc, countdown=60)
|
||||
Reference in New Issue
Block a user