First Commit
This commit is contained in:
commit
ce0758fbbb
496 changed files with 52062 additions and 0 deletions
0
users/__init__.py
Executable file
0
users/__init__.py
Executable file
30
users/admin.py
Executable file
30
users/admin.py
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# admin.py des utilisateurs
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import Group
|
||||
from .models import User
|
||||
|
||||
# Register your models here.
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
model = User
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'username_decoration', 'password')}),
|
||||
('Informations personnelles', {'fields': ('first_name', 'last_name', 'email', 'theme', 'avatar', 'border_avatar', 'biography', 'birth_date')}),
|
||||
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
|
||||
('Dates importantes', {'fields': ('last_login', 'date_joined')}),
|
||||
)
|
||||
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('username', 'password1', 'password2'),
|
||||
}),
|
||||
)
|
||||
|
||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
||||
|
||||
search_fields = ('username', 'email', 'first_name', 'last_name')
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
6
users/apps.py
Executable file
6
users/apps.py
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "users"
|
||||
13
users/decorators.py
Executable file
13
users/decorators.py
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
def groups_required(*group_names):
|
||||
def decorator(view_func):
|
||||
@login_required
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if any(request.user.groups.filter(name=group_name).exists() for group_name in group_names):
|
||||
return view_func(request, *args, **kwargs)
|
||||
else:
|
||||
raise PermissionDenied
|
||||
return _wrapped_view
|
||||
return decorator
|
||||
79
users/forms.py
Executable file
79
users/forms.py
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
from django import forms
|
||||
|
||||
from .models import User
|
||||
|
||||
class UserRegistrationForm(forms.Form):
|
||||
username = forms.CharField(
|
||||
label='',
|
||||
max_length=150,
|
||||
widget=forms.TextInput(attrs={'class': 'form-group', 'placeholder': 'Pseudo'})
|
||||
)
|
||||
email = forms.EmailField(
|
||||
label='',
|
||||
widget=forms.EmailInput(attrs={'class': 'form-group', 'placeholder': 'Email'})
|
||||
)
|
||||
password1 = forms.CharField(
|
||||
label='',
|
||||
widget=forms.PasswordInput(attrs={'class': 'form-group', 'placeholder': 'Mot de passe'})
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label='',
|
||||
widget=forms.PasswordInput(attrs={'class': 'form-group', 'placeholder': 'Mot de passe (vérification)'})
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password1 = cleaned_data.get("password1")
|
||||
password2 = cleaned_data.get("password2")
|
||||
|
||||
if password1 and password2 and password1 != password2:
|
||||
raise forms.ValidationError("Passwords do not match")
|
||||
|
||||
class UserLoginForm(forms.Form):
|
||||
username = forms.CharField(
|
||||
label='',
|
||||
max_length=150,
|
||||
widget=forms.TextInput(attrs={'class': 'form-group', 'placeholder': 'Pseudo'})
|
||||
)
|
||||
password = forms.CharField(
|
||||
label='',
|
||||
widget=forms.PasswordInput(attrs={'class': 'form-group', 'placeholder': 'Mot de passe'})
|
||||
)
|
||||
|
||||
class UserUpdateForm(forms.ModelForm):
|
||||
theme = forms.CharField(
|
||||
# On créer une liste de choix pour le thème
|
||||
label='Thème',
|
||||
widget=forms.Select(choices=[('default', 'Thème par defaut'), ('80s', 'Thème années 80'), ('00s', 'Thème années 2000 (adapté pour mobiles)')]),
|
||||
)
|
||||
class Meta:
|
||||
model = User
|
||||
labels = {
|
||||
'avatar': 'Avatar',
|
||||
'email': 'Email',
|
||||
'first_name': 'Prénom',
|
||||
'last_name': 'Nom',
|
||||
'biography': 'Biographie',
|
||||
}
|
||||
fields = ['avatar', 'email', 'first_name', 'last_name', 'biography']
|
||||
|
||||
class ProfileUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['avatar', 'biography']
|
||||
|
||||
class CompleteProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['avatar', 'birth_date', 'biography']
|
||||
|
||||
class PostForm(forms.Form):
|
||||
title = forms.CharField(
|
||||
max_length=150,
|
||||
label='',
|
||||
widget=forms.TextInput(attrs={'placeholder': 'Titre de l\'article'})
|
||||
)
|
||||
content = forms.CharField(
|
||||
label='',
|
||||
widget=forms.Textarea(attrs={'placeholder': 'Contenu de l\'article'})
|
||||
)
|
||||
113
users/middleware.py
Executable file
113
users/middleware.py
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
# yourapp/middleware.py
|
||||
from django.shortcuts import redirect
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from .models import User, VisitorStats
|
||||
from messagerie.models import PrivateMessage, PrivateMessageSubject
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.contrib import messages
|
||||
|
||||
# On vérifie que l'user existe dans la table user_level. Si pas, on le redirige vers la page tuto de la nouvelle feature
|
||||
class UserLevelMiddleware(MiddlewareMixin):
|
||||
# On vérifie que l'on est pas sur la page de tuto de la nouvelle feature
|
||||
def process_request(self, request):
|
||||
if request.user.is_authenticated:
|
||||
if request.path != '/users/new/feature':
|
||||
if not request.user.levels.exists():
|
||||
return redirect('new_feature_user')
|
||||
|
||||
if request.path != '/users/new/feature':
|
||||
if not request.user.inventory.exists():
|
||||
return redirect('new_feature_user')
|
||||
|
||||
class UserLevelUpMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
if request.user.is_authenticated and request.user.levels.exists():
|
||||
if not request.session.get('level_up_processed', False):
|
||||
user = request.user
|
||||
user_level = user.level # Accède à l'objet UserLevel associé à l'utilisateur
|
||||
|
||||
# Calcul de l'XP requise pour le prochain niveau
|
||||
def xp_required(level):
|
||||
return int(33.33 * (level ** 2) - 16.66 * level)
|
||||
|
||||
if user_level.experience >= xp_required(user_level.level):
|
||||
user_level.level += 1
|
||||
user_level.save()
|
||||
messages.success(request, f"Bravo ! Vous avez atteint le niveau {user_level.level} !")
|
||||
|
||||
subject = PrivateMessageSubject.objects.create(
|
||||
receiver=user,
|
||||
sender=User.objects.get(username='RetroBot'),
|
||||
subject=f"Bravo { request.user } ! Tu as atteint un nouveau niveau !"
|
||||
)
|
||||
|
||||
PrivateMessage.objects.create(
|
||||
subject=subject,
|
||||
author=User.objects.get(username='RetroBot'),
|
||||
message=f"""[b]🎉 Félicitations {request.user}! 🎉[/b]
|
||||
|
||||
Tu viens de passer au [b]Niveau Supérieur[/b] ! Tu es maintenant [b]Niveau { user_level.level }[/b] !
|
||||
|
||||
💰 En récompense, tu gagnes [b]20 Or[/b] ! 💰
|
||||
|
||||
[i]Continue tes efforts, aventurier, et vise toujours plus haut ![/i]
|
||||
"""
|
||||
)
|
||||
|
||||
# Marquez le niveau comme traité pour cette requête
|
||||
request.session['level_up_processed'] = True
|
||||
|
||||
# Ajouter 20 Or à l'inventaire de l'utilisateur
|
||||
if request.user.inventory.exists():
|
||||
inventory_item = request.user.inventory.get(item__name='Or')
|
||||
inventory_item.quantity += 20
|
||||
inventory_item.save()
|
||||
|
||||
# On affiche l'experience restante pour le prochain niveau
|
||||
request.user.experience_left = xp_required(user_level.level) - user_level.experience
|
||||
else:
|
||||
# On affiche l'expérience restante pour le prochain niveau même si le niveau n'a pas changé
|
||||
user_level = request.user.level
|
||||
def xp_required(level):
|
||||
return int(33.33 * (level ** 2) - 16.66 * level)
|
||||
request.user.experience_left = xp_required(user_level.level) - user_level.experience
|
||||
|
||||
class UserStatsMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
# Nombre total d'utilisateurs
|
||||
total_users = User.objects.count()
|
||||
|
||||
# Dernier utilisateur inscrit
|
||||
last_user = User.objects.latest('date_joined') if total_users > 0 else None
|
||||
|
||||
# Gestion des visiteurs actuels
|
||||
current_time = timezone.now()
|
||||
visitor_key = request.session.session_key or request.META.get('REMOTE_ADDR')
|
||||
|
||||
# Mise à jour du cache des visiteurs actuels
|
||||
active_visitors = cache.get('active_visitors', set())
|
||||
active_visitors.add(visitor_key)
|
||||
cache.set('active_visitors', active_visitors, 300) # expire après 5 minutes
|
||||
visitor_count = len(active_visitors)
|
||||
|
||||
# Gestion du total des visiteurs
|
||||
stats, created = VisitorStats.objects.get_or_create(pk=1)
|
||||
if 'first_visit' not in request.session:
|
||||
request.session['first_visit'] = True
|
||||
stats.total_visitors += 1
|
||||
stats.save()
|
||||
total_visitor_count = stats.total_visitors
|
||||
|
||||
# Si l'utilisateur est authentifié
|
||||
if request.user.is_authenticated:
|
||||
theme_active = request.user.theme
|
||||
else:
|
||||
theme_active = '00s'
|
||||
|
||||
# Ajouter les variables à l'objet request
|
||||
request.total_users = total_users
|
||||
request.last_user = last_user
|
||||
request.visitor_count = visitor_count
|
||||
request.total_visitor_count = total_visitor_count
|
||||
request.theme_active = theme_active
|
||||
138
users/migrations/0001_initial.py
Normal file
138
users/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-21 18:39
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="User",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"avatar",
|
||||
models.ImageField(
|
||||
default="avatars/default.png", upload_to="avatars/"
|
||||
),
|
||||
),
|
||||
("biography", models.TextField(default="Pas de bio")),
|
||||
("birth_date", models.DateField(blank=True, null=True)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="The groups this user belongs to.",
|
||||
related_name="customuser_set",
|
||||
to="auth.group",
|
||||
verbose_name="groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_permissions",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="customuser_permissions_set",
|
||||
to="auth.permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
("objects", django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-21 21:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={
|
||||
"ordering": ["username"],
|
||||
"verbose_name": "Utilisateur",
|
||||
"verbose_name_plural": "Utilisateurs",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="avatar",
|
||||
field=models.ImageField(default="default.gif", upload_to="avatars/"),
|
||||
),
|
||||
]
|
||||
18
users/migrations/0003_user_active.py
Normal file
18
users/migrations/0003_user_active.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-22 10:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0002_alter_user_options_alter_user_avatar"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="active",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
25
users/migrations/0004_user_theme_alter_user_avatar.py
Normal file
25
users/migrations/0004_user_theme_alter_user_avatar.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 4.2.16 on 2024-10-23 17:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0003_user_active"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="theme",
|
||||
field=models.CharField(default="default", max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="avatar",
|
||||
field=models.ImageField(
|
||||
default="avatars/default.gif", upload_to="avatars/"
|
||||
),
|
||||
),
|
||||
]
|
||||
18
users/migrations/0005_alter_user_active.py
Normal file
18
users/migrations/0005_alter_user_active.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-16 13:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0004_user_theme_alter_user_avatar'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='active',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
24
users/migrations/0006_alter_user_avatar_alter_user_email.py
Normal file
24
users/migrations/0006_alter_user_avatar_alter_user_email.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-23 14:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import users.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0005_alter_user_active'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='avatar',
|
||||
field=models.ImageField(default='media/avatars/default.gif', upload_to=users.models.user_avatar_path),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=254),
|
||||
),
|
||||
]
|
||||
19
users/migrations/0007_alter_user_avatar.py
Normal file
19
users/migrations/0007_alter_user_avatar.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 4.2.17 on 2024-12-23 16:48
|
||||
|
||||
from django.db import migrations, models
|
||||
import users.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0006_alter_user_avatar_alter_user_email'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='avatar',
|
||||
field=models.ImageField(default='avatars/default.gif', upload_to=users.models.user_avatar_path),
|
||||
),
|
||||
]
|
||||
21
users/migrations/0008_visitorstats.py
Normal file
21
users/migrations/0008_visitorstats.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-04 12:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0007_alter_user_avatar'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VisitorStats',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('total_visitors', models.PositiveIntegerField(default=0)),
|
||||
('last_reset', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
29
users/migrations/0009_userlevel.py
Normal file
29
users/migrations/0009_userlevel.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 17:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0008_visitorstats'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserLevel',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('level', models.PositiveIntegerField(default=1)),
|
||||
('experience', models.PositiveIntegerField(default=0)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='levels', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Niveau utilisateur',
|
||||
'verbose_name_plural': 'Niveaux utilisateurs',
|
||||
'ordering': ['level'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-06 18:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0009_userlevel'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=150),
|
||||
),
|
||||
]
|
||||
30
users/migrations/0011_userinventory.py
Normal file
30
users/migrations/0011_userinventory.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-07 10:47
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0001_initial'),
|
||||
('users', '0010_alter_user_first_name_alter_user_last_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='UserInventory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.PositiveIntegerField(default=1)),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.item')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventory', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Inventaire utilisateur',
|
||||
'verbose_name_plural': 'Inventaires utilisateurs',
|
||||
'ordering': ['item'],
|
||||
},
|
||||
),
|
||||
]
|
||||
18
users/migrations/0012_user_border_avatar.py
Normal file
18
users/migrations/0012_user_border_avatar.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-07 13:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0011_userinventory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='border_avatar',
|
||||
field=models.CharField(default='', max_length=50),
|
||||
),
|
||||
]
|
||||
18
users/migrations/0013_user_username_decoration.py
Normal file
18
users/migrations/0013_user_username_decoration.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-07 13:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0012_user_border_avatar'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='username_decoration',
|
||||
field=models.CharField(default='', max_length=50),
|
||||
),
|
||||
]
|
||||
18
users/migrations/0014_alter_user_username_decoration.py
Normal file
18
users/migrations/0014_alter_user_username_decoration.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-07 16:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0013_user_username_decoration'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='username_decoration',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
18
users/migrations/0015_alter_user_border_avatar.py
Normal file
18
users/migrations/0015_alter_user_border_avatar.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.17 on 2025-01-07 16:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0014_alter_user_username_decoration'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='border_avatar',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
]
|
||||
0
users/migrations/__init__.py
Executable file
0
users/migrations/__init__.py
Executable file
87
users/models.py
Executable file
87
users/models.py
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
from django.contrib.auth.models import AbstractUser, Group, Permission
|
||||
from django.db import models
|
||||
from shop.models import Item
|
||||
|
||||
def user_avatar_path(instance, filename):
|
||||
return f'avatars/{instance.id}/{filename}'
|
||||
|
||||
class User(AbstractUser):
|
||||
avatar = models.ImageField(upload_to=user_avatar_path, default='avatars/default.gif')
|
||||
email = models.EmailField(unique=False)
|
||||
biography = models.TextField(default='Pas de bio')
|
||||
first_name = models.CharField(max_length=30, blank=True)
|
||||
last_name = models.CharField(max_length=150, blank=True)
|
||||
birth_date = models.DateField(null=True, blank=True)
|
||||
active = models.BooleanField(default=False)
|
||||
theme = models.CharField(max_length=50, default='default')
|
||||
border_avatar = models.CharField(max_length=50, blank=True, null=True)
|
||||
username_decoration = models.CharField(max_length=50, blank=True, null=True)
|
||||
|
||||
groups = models.ManyToManyField(
|
||||
Group,
|
||||
related_name='customuser_set', # Ajoutez un related_name unique
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to.',
|
||||
verbose_name='groups',
|
||||
)
|
||||
user_permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
related_name='customuser_permissions_set', # Ajoutez un related_name unique
|
||||
blank=True,
|
||||
help_text='Specific permissions for this user.',
|
||||
verbose_name='user permissions',
|
||||
)
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return self.levels.first()
|
||||
|
||||
@property
|
||||
def experience(self):
|
||||
return self.levels.get(user=self).experience
|
||||
|
||||
@property
|
||||
def inventory(self):
|
||||
return self.inventory.all()
|
||||
|
||||
@property
|
||||
def money(self):
|
||||
return self.inventory.get(item__name='Or').quantity
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Utilisateur'
|
||||
verbose_name_plural = 'Utilisateurs'
|
||||
ordering = ['username']
|
||||
|
||||
class VisitorStats(models.Model):
|
||||
total_visitors = models.PositiveIntegerField(default=0)
|
||||
last_reset = models.DateTimeField(auto_now=True)
|
||||
|
||||
class UserLevel(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='levels')
|
||||
level = models.PositiveIntegerField(default=1)
|
||||
experience = models.PositiveIntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return self.level
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Niveau utilisateur'
|
||||
verbose_name_plural = 'Niveaux utilisateurs'
|
||||
ordering = ['level']
|
||||
|
||||
class UserInventory(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='inventory')
|
||||
item = models.ForeignKey(Item, on_delete=models.CASCADE)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
|
||||
def __str__(self):
|
||||
return self.item.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Inventaire utilisateur'
|
||||
verbose_name_plural = 'Inventaires utilisateurs'
|
||||
ordering = ['item']
|
||||
0
users/templatetags/__init__.py
Executable file
0
users/templatetags/__init__.py
Executable file
7
users/templatetags/users_custom_tags.py
Executable file
7
users/templatetags/users_custom_tags.py
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name='has_group')
|
||||
def has_group(user, group_name):
|
||||
return user.groups.filter(name=group_name).exists()
|
||||
3
users/tests.py
Executable file
3
users/tests.py
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
22
users/urls.py
Executable file
22
users/urls.py
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
from django.urls import path
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib.auth import views as auth_views
|
||||
|
||||
from passion_retro import settings
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('activate/<uidb64>/<token>/', views.activate, name='activate'),
|
||||
path("login/", views.login, name="login"),
|
||||
path("register/", views.register, name="register"),
|
||||
path("profile/", views.profile, name="profile"),
|
||||
path("profile/update/", views.profile_update, name="profile_update"),
|
||||
path("profile/contributions/", views.contributions, name="contributions"),
|
||||
path("profile/<str:user_id>/", views.another_profile, name="profile"),
|
||||
path("contribute/", views.contribute, name="contribute"),
|
||||
path("contribute/<str:type>/", views.form_contribute, name="form_contribute"),
|
||||
path("new/feature", views.new_feature_user, name="new_feature_user"),
|
||||
path("item/use/<int:item_id>/", views.use_item, name="use_item"),
|
||||
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
]
|
||||
308
users/views.py
Executable file
308
users/views.py
Executable file
|
|
@ -0,0 +1,308 @@
|
|||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import authenticate, login as auth_login
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .forms import PostForm, UserRegistrationForm, UserLoginForm, UserUpdateForm, ProfileUpdateForm
|
||||
from posts.models import Post
|
||||
from django.utils.text import slugify
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from .models import User, UserLevel
|
||||
from django.db.models import F
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
def register(request):
|
||||
from messagerie.models import PrivateMessageSubject, PrivateMessage
|
||||
if request.user.is_authenticated:
|
||||
return redirect('profile')
|
||||
|
||||
if request.method == "POST":
|
||||
form = UserRegistrationForm(request.POST)
|
||||
recaptcha = request.POST.get('g-recaptcha-response')
|
||||
if form.is_valid():
|
||||
|
||||
url = 'https://www.google.com/recaptcha/api/siteverify'
|
||||
values = {
|
||||
'secret': settings.GOOGLE_PRIVATE_KEY,
|
||||
'response': recaptcha
|
||||
}
|
||||
data = urllib.parse.urlencode(values).encode()
|
||||
req = urllib.request.Request(url, data=data)
|
||||
response = urllib.request.urlopen(req)
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
if result['success']:
|
||||
user = User.objects.create_user(
|
||||
username=form.cleaned_data['username'],
|
||||
email=form.cleaned_data['email'],
|
||||
password=form.cleaned_data['password1'],
|
||||
theme='00s',
|
||||
active=True
|
||||
)
|
||||
|
||||
messages.success(request, f"Bonjour et bienvenue {user.username} ! Ton compte à été créer avec succès. Tu peux désormais te connecter.")
|
||||
|
||||
subject = PrivateMessageSubject.objects.create(
|
||||
receiver=user,
|
||||
sender=User.objects.get(username='RetroBot'),
|
||||
subject="Bienvenue sur PassionRetro !"
|
||||
)
|
||||
|
||||
PrivateMessage.objects.create(
|
||||
subject=subject,
|
||||
author=User.objects.get(username='RetroBot'),
|
||||
message=f"""[b]Bienvenue sur Passion Retro ![/b]
|
||||
|
||||
Salut [b]{user.username}[/b] !,
|
||||
|
||||
Merci de nous avoir rejoints dans cette aventure dédiée aux passionnés de rétro ! Que tu sois fan de consoles vintage, collectionneur d'objets d'époque ou simple curieux, tu es ici chez toi.
|
||||
|
||||
✨ [b]Découvre tout ce que Passion Retro a à offrir :[/b]
|
||||
Plonge dans nos articles pour en apprendre plus sur les trésors du passé, participe aux discussions sur le forum et teste tes connaissances avec nos jeux rétro. Chaque section est là pour te permettre de partager ta passion et d'en apprendre davantage.
|
||||
|
||||
🚀 [b]Rejoins la communauté :[/b]
|
||||
Ton avis et tes contributions sont précieux ! N’hésite pas à lancer une discussion sur le forum, à réagir aux articles ou à défier les autres membres sur nos jeux. Plus nous sommes actifs, plus l’expérience sera enrichissante pour tous.
|
||||
|
||||
Si tu as des questions ou des suggestions pour améliorer le site, contacte-nous. Nous sommes là pour t'accompagner !
|
||||
|
||||
Encore une fois, bienvenue parmi nous et prépare-toi à replonger dans l’univers du rétro !
|
||||
|
||||
À bientôt,
|
||||
[b]L'équipe Passion Retro[/b]""")
|
||||
|
||||
return redirect('login')
|
||||
else:
|
||||
messages.error(request, f"On y est presque ! Vérifie bien le captcha pour finaliser ton inscription.")
|
||||
|
||||
form = UserRegistrationForm()
|
||||
return render(request, 'users/register.html', {'form': form, 'GOOGLE_PUBLIC_KEY': settings.GOOGLE_PUBLIC_KEY})
|
||||
|
||||
def register_with_token(request):
|
||||
# Si l'utilisateur est deja connecté, on le redirige vers la page de pr>
|
||||
if request.user.is_authenticated:
|
||||
return redirect('profile')
|
||||
if request.method == 'POST':
|
||||
form = UserRegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = User.objects.create_user(
|
||||
username=form.cleaned_data['username'],
|
||||
email=form.cleaned_data['email'],
|
||||
password=form.cleaned_data['password1']
|
||||
)
|
||||
genToken = PasswordResetTokenGenerator()
|
||||
token = genToken.make_token(user)
|
||||
uid = urlsafe_base64_encode(force_bytes(user.pk))
|
||||
|
||||
activation_link = request.build_absolute_uri(f"/users/activate/{uid}/{token}")
|
||||
|
||||
email_subject = 'Activation de votre compte'
|
||||
email_body = render_to_string('emails/activation_account.html', {
|
||||
'user': user,
|
||||
'activation_link': activation_link,
|
||||
})
|
||||
|
||||
send_mail(
|
||||
email_subject,
|
||||
email_body,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[user.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
messages.success(request, f"Ton compte a été créé avec succès, {user.username}! Un email d'activation t'a été envoyé pour valider ton inscription.")
|
||||
|
||||
return redirect('login')
|
||||
else:
|
||||
form = UserRegistrationForm()
|
||||
return render(request, 'users/register.html', {'form': form})
|
||||
|
||||
def activate(request, uidb64, token):
|
||||
uid = None # Initialisation de la variable uid
|
||||
try:
|
||||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||||
user = User.objects.get(pk=uid)
|
||||
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
|
||||
user = None
|
||||
|
||||
token_generator = PasswordResetTokenGenerator()
|
||||
if user is not None and token_generator.check_token(user, token):
|
||||
user.active = True
|
||||
user.save()
|
||||
messages.success(request, "Votre compte a été activé avec succès.")
|
||||
return redirect('login')
|
||||
else:
|
||||
messages.error(request, "Le lien d'activation est invalide.")
|
||||
return redirect('register')
|
||||
|
||||
def login(request):
|
||||
if request.method == 'POST':
|
||||
form = UserLoginForm(data=request.POST)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data.get('username')
|
||||
password = form.cleaned_data.get('password')
|
||||
user = authenticate(request, username=username, password=password)
|
||||
if user is None:
|
||||
messages.error(request, "Nom d'utilisateur ou mot de passe incorrect.")
|
||||
return redirect('login')
|
||||
|
||||
if user.active == False:
|
||||
messages.error(request, "Votre compte n'est pas activé. Veuillez vérifier votre boîte mail.")
|
||||
return redirect('login')
|
||||
|
||||
auth_login(request, user)
|
||||
messages.success(request, f"Bienvenue, {username}!")
|
||||
return redirect('home')
|
||||
else:
|
||||
messages.error(request, "Nom d'utilisateur ou mot de passe incorrect.")
|
||||
else:
|
||||
form = UserLoginForm()
|
||||
|
||||
return render(request, 'users/login.html', {'form': form})
|
||||
|
||||
def profile(request):
|
||||
from forum.models import Topic, Post
|
||||
from posts.models import Post
|
||||
user = User.objects.get(id=request.user.id)
|
||||
# On compte le nombre de topics du forum de l'utilisateur
|
||||
topics = Topic.objects.filter(author=user).count()
|
||||
forum_posts = Post.objects.filter(author=user).count()
|
||||
posts = Post.objects.filter(author=user).count()
|
||||
|
||||
return render(request, 'users/profile.html', { 'user': user, 'topics': topics, 'forum_posts': forum_posts, 'posts': posts })
|
||||
|
||||
def another_profile(request, user_id):
|
||||
from forum.models import Topic, Post
|
||||
from posts.models import Post
|
||||
user = User.objects.get(id=user_id)
|
||||
# On compte le nombre de topics du forum de l'utilisateur
|
||||
topics = Topic.objects.filter(author=user).count()
|
||||
forum_posts = Post.objects.filter(author=user).count()
|
||||
posts = Post.objects.filter(author=user).count()
|
||||
|
||||
return render(request, 'users/profile.html', {'user': user, 'topics': topics, 'forum_posts': forum_posts, 'posts': posts})
|
||||
|
||||
@login_required(login_url='login')
|
||||
def profile_update(request):
|
||||
if request.method == 'POST':
|
||||
user_form = UserUpdateForm(request.POST, request.FILES, instance=request.user)
|
||||
if user_form.is_valid():
|
||||
user_form.save()
|
||||
messages.success(request, 'Votre profil a été mis à jour avec succès !')
|
||||
return redirect('profile')
|
||||
else:
|
||||
user_form = UserUpdateForm(instance=request.user)
|
||||
return render(request, 'users/profile_update.html', {'user_form': user_form})
|
||||
|
||||
@login_required
|
||||
def contribute(request):
|
||||
return render(request, "users/contribute.html")
|
||||
|
||||
@login_required(login_url='register')
|
||||
def form_contribute(request, type):
|
||||
if request.method == 'POST':
|
||||
# Initialisation des articles
|
||||
form = PostForm(request.POST)
|
||||
posts = []
|
||||
|
||||
# Traite l'article principal (parent)
|
||||
main_title = request.POST.get('title') # Titre principal du formulaire Django
|
||||
main_content = request.POST.get('content') # Contenu principal
|
||||
parent_post = None
|
||||
|
||||
if main_title and main_content:
|
||||
# Crée le parent
|
||||
parent_post = Post.objects.create(
|
||||
title=main_title,
|
||||
slug=slugify(main_title),
|
||||
content=main_content,
|
||||
type=type,
|
||||
author=request.user,
|
||||
contribution=True,
|
||||
parent=True
|
||||
)
|
||||
posts.append(parent_post)
|
||||
UserLevel.objects.update(user=request.user, experience=F('experience') + 10)
|
||||
|
||||
# Vérifie si des articles dynamiques existent (type_form = multiple)
|
||||
type_form = request.POST.get('type_form')
|
||||
|
||||
if type_form == 'multiple':
|
||||
# Parcourt les champs dynamiques ajoutés en JS
|
||||
for key in request.POST:
|
||||
if key.startswith('title-post-'):
|
||||
# Récupère le numéro de l'article
|
||||
article_number = key.split('-')[-1]
|
||||
|
||||
# Récupère les données de l'article enfant
|
||||
title = request.POST.get(f'title-post-{article_number}')
|
||||
content = request.POST.get(f'content-post-{article_number}')
|
||||
|
||||
if title and content:
|
||||
# Crée l'article enfant
|
||||
child_post = Post.objects.create(
|
||||
title=title,
|
||||
slug=slugify(title),
|
||||
content=content,
|
||||
type=type,
|
||||
author=request.user,
|
||||
contribution=True,
|
||||
parent=False,
|
||||
post_parent=parent_post
|
||||
)
|
||||
posts.append(child_post)
|
||||
UserLevel.objects.update(user=request.user, experience=F('experience') + 5)
|
||||
|
||||
messages.success(request, "Vos articles ont été soumis avec succès !")
|
||||
|
||||
context = {
|
||||
'type': type,
|
||||
'form': PostForm(),
|
||||
}
|
||||
return render(request, "users/form_contribute.html", context)
|
||||
|
||||
@login_required()
|
||||
def contributions(request):
|
||||
posts = Post.objects.filter(author=request.user)
|
||||
|
||||
return render(request, "users/contributions.html", {'posts':posts})
|
||||
|
||||
@login_required
|
||||
def new_feature_user(request):
|
||||
# On créer une entree dans la table user_level pour l'utilisateur si il n'en a pas
|
||||
if not request.user.level:
|
||||
request.user.levels.create(level=1)
|
||||
return render(request, "features/new_feature_user_level.html")
|
||||
|
||||
if not request.user.inventory.exists():
|
||||
request.user.inventory.create(item_id=1, quantity=100)
|
||||
return render(request, "features/new_feature_user_inventory.html")
|
||||
|
||||
@login_required
|
||||
def use_item(request, item_id):
|
||||
item = request.user.inventory.get(item_id=item_id)
|
||||
|
||||
# On slugify le nom de l'item pour l'utiliser dans les conditions
|
||||
item_name = slugify(item.item.name)
|
||||
|
||||
if item.quantity > 0:
|
||||
if item.item.category.name == 'cadres':
|
||||
request.user.border_avatar = item_name
|
||||
|
||||
if item.item.category.name == 'themes':
|
||||
request.user.theme_active = item_name
|
||||
|
||||
if item.item.category.name == 'Décoration pseudo':
|
||||
request.user.username_decoration = item_name
|
||||
|
||||
request.user.save()
|
||||
|
||||
messages.success(request, f"Vous avez utilisé {item.item.name}.")
|
||||
else:
|
||||
messages.error(request, f"Vous n'avez pass de {item.item.name}.")
|
||||
return redirect('profile')
|
||||
Loading…
Add table
Add a link
Reference in a new issue