init
This commit is contained in:
0
app/user/__init__.py
Normal file
0
app/user/__init__.py
Normal file
51
app/user/admin.py
Normal file
51
app/user/admin.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
|
||||
from .forms import UserCreationForm, UserChangeForm
|
||||
from .models import User, FriendRequest, Invitation
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
# add_form = UserCreationForm
|
||||
form = UserChangeForm
|
||||
fieldsets = BaseUserAdmin.fieldsets + (
|
||||
["Custom Fields", {
|
||||
"fields": ["max_size", "friends"]
|
||||
}]
|
||||
,)
|
||||
list_display = ["username", "email", "is_superuser", "is_active", "is_staff", "display_max_size", "size_used"]
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
"classes": ("wide",),
|
||||
"fields": ("username", "email", "max_size", "password1", "password2"),
|
||||
}),
|
||||
)
|
||||
|
||||
def display_max_size(self, obj: User):
|
||||
return filesizeformat(obj.max_size)
|
||||
display_max_size.short_description = "Max size"
|
||||
|
||||
def size_used(self, obj: User):
|
||||
return filesizeformat(obj.size_used)
|
||||
size_used.short_description = "Size used"
|
||||
|
||||
def save_formset(self, request, form, formset, change):
|
||||
print("save_formset")
|
||||
return super().save_formset(request, form, formset, change)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
print("save_model")
|
||||
return super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
|
||||
@admin.register(Invitation)
|
||||
class InvitationAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(FriendRequest)
|
||||
class FriendRequestAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
6
app/user/apps.py
Normal file
6
app/user/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'user'
|
||||
27
app/user/forms.py
Normal file
27
app/user/forms.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django import forms
|
||||
from django.contrib.auth import forms as base_auth_forms
|
||||
from django.contrib.auth.forms import AdminUserCreationForm
|
||||
|
||||
from user.models import User
|
||||
|
||||
|
||||
class RegisterForm(base_auth_forms.UserCreationForm):
|
||||
email = forms.EmailField(required=True)
|
||||
|
||||
class Meta(base_auth_forms.UserCreationForm.Meta):
|
||||
model = User
|
||||
|
||||
|
||||
class UserCreationForm(AdminUserCreationForm):
|
||||
max_size = forms.IntegerField(required=True)
|
||||
email = forms.EmailField(required=True)
|
||||
class Meta(base_auth_forms.UserCreationForm):
|
||||
model = User
|
||||
fields = base_auth_forms.BaseUserCreationForm.Meta.fields + ("max_size",)
|
||||
|
||||
|
||||
class UserChangeForm(base_auth_forms.UserChangeForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["max_size"]
|
||||
|
||||
46
app/user/management/commands/import_old_users.py
Normal file
46
app/user/management/commands/import_old_users.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
import json
|
||||
import base64
|
||||
import sys
|
||||
|
||||
from user.models import User
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
# comment utiliser :
|
||||
# se connect en bash à l'ancien environnement, ensuite taper : python manage.py dumpdata member.User | base64 -w 0
|
||||
# se connecter en bash au nouvel environnement et ensuite taper : echo -e "le_précédent_output..." | python manage.py import_old_users
|
||||
|
||||
inp = ""
|
||||
for i in sys.stdin:
|
||||
inp += i
|
||||
|
||||
old_users = json.loads(base64.b64decode(inp.encode("utf-8")))
|
||||
|
||||
old_new_users_maps = {}
|
||||
old_friends = {}
|
||||
|
||||
for data in old_users:
|
||||
user_data = data["fields"]
|
||||
user_pk = data["pk"]
|
||||
if User.objects.filter(username__iexact=user_data["username"]).exists():
|
||||
old_new_users_maps[user_pk] = User.objects.filter(username__iexact=user_data["username"]).get()
|
||||
else:
|
||||
old_new_users_maps[user_pk] = User.objects.create(
|
||||
email=user_data["email"],
|
||||
is_active=user_data["is_active"],
|
||||
is_staff=user_data["is_staff"],
|
||||
is_superuser=user_data["is_superuser"],
|
||||
username=user_data["username"],
|
||||
password=user_data["password"],
|
||||
max_size=user_data["limit_size"]
|
||||
)
|
||||
old_friends[user_pk] = user_data["friends"]
|
||||
|
||||
for old_user, friends in old_friends.items():
|
||||
current_user = old_new_users_maps[old_user]
|
||||
for friend in friends:
|
||||
if not current_user.friends.filter(id=old_new_users_maps[friend].id).exists():
|
||||
current_user.friends.add(old_new_users_maps[friend])
|
||||
72
app/user/migrations/0001_initial.py
Normal file
72
app/user/migrations/0001_initial.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Generated by Django 5.1.6 on 2025-03-04 23:41
|
||||
|
||||
import django.contrib.auth.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import user.models
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
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')),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('max_size', models.PositiveBigIntegerField(default=53687091200)),
|
||||
('is_trusted', models.BooleanField(default=False)),
|
||||
('friends', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
},
|
||||
managers=[
|
||||
('objects', user.models.UsernameUserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Invitation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token', models.UUIDField(default=uuid.uuid4)),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations', to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invitation', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FriendRequest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_request_receives', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friend_request_sends', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('sender', 'receiver')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
app/user/migrations/__init__.py
Normal file
0
app/user/migrations/__init__.py
Normal file
76
app/user/models.py
Normal file
76
app/user/models.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
||||
from django.db.models import Sum
|
||||
|
||||
import uuid
|
||||
from functools import cached_property
|
||||
from torrent.models import Torrent
|
||||
|
||||
|
||||
class UsernameUserManager(BaseUserManager):
|
||||
use_in_migrations = True
|
||||
|
||||
def _create_user(self, username, email, password, **extra_fields):
|
||||
if not username:
|
||||
raise ValueError("Un username doit être défini")
|
||||
if not email:
|
||||
raise ValueError("Un email doit être défini")
|
||||
|
||||
email = self.normalize_email(email)
|
||||
user = self.model(username=username, email=email, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self.db)
|
||||
return user
|
||||
|
||||
def create_user(self, username, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", False)
|
||||
extra_fields.setdefault("is_superuser", False)
|
||||
return self._create_user(username, email, password, **extra_fields)
|
||||
|
||||
def create_superuser(self, username, email, password, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
if extra_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser doit être staff à True")
|
||||
if extra_fields.get("is_superuser") is not True:
|
||||
raise ValueError("SuperUser doit être is_superuser à True")
|
||||
|
||||
return self._create_user(username, email, password, **extra_fields)
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
email = models.EmailField(unique=True)
|
||||
|
||||
max_size = models.PositiveBigIntegerField(default=53687091200)
|
||||
friends = models.ManyToManyField("self", blank=True)
|
||||
is_trusted = models.BooleanField(default=False)
|
||||
|
||||
objects = UsernameUserManager()
|
||||
|
||||
@cached_property
|
||||
def size_used(self):
|
||||
if hasattr(self, "total_size"):
|
||||
return self.total_size
|
||||
else:
|
||||
return Torrent.objects.filter(user=self).aggregate(total_size=Sum("size", default=0))["total_size"]
|
||||
|
||||
@property
|
||||
def min_infos(self):
|
||||
return {"username": self.username, "id": self.id}
|
||||
|
||||
|
||||
class FriendRequest(models.Model):
|
||||
sender = models.ForeignKey("User", on_delete=models.CASCADE, related_name="friend_request_sends")
|
||||
receiver = models.ForeignKey("User", on_delete=models.CASCADE, related_name="friend_request_receives")
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ("sender", "receiver")
|
||||
|
||||
|
||||
class Invitation(models.Model):
|
||||
created_by = models.ForeignKey("User", models.CASCADE, related_name="invitations")
|
||||
token = models.UUIDField(default=uuid.uuid4)
|
||||
user = models.OneToOneField("User", models.CASCADE, related_name="invitation", null=True, blank=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
32
app/user/serializers.py
Normal file
32
app/user/serializers.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import User, FriendRequest, Invitation
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
count_torrent = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "username", "count_torrent"]
|
||||
|
||||
|
||||
class FriendRequestSerializer(serializers.ModelSerializer):
|
||||
username = serializers.CharField(source="sender.username")
|
||||
|
||||
class Meta:
|
||||
model = FriendRequest
|
||||
fields = ["id", "username"]
|
||||
|
||||
|
||||
class InvitationSerializer(serializers.ModelSerializer):
|
||||
created_by = serializers.PrimaryKeyRelatedField(
|
||||
default=serializers.CurrentUserDefault(),
|
||||
queryset=User.objects.all(),
|
||||
)
|
||||
created_by_obj = UserSerializer(read_only=True, source="created_by")
|
||||
url = serializers.URLField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Invitation
|
||||
fields = "__all__"
|
||||
1
app/user/templates/user/layout.html
Normal file
1
app/user/templates/user/layout.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends "base.html" %}
|
||||
9
app/user/templates/user/login.html
Normal file
9
app/user/templates/user/login.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends "user/layout.html" %}
|
||||
{% load django_vite %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
let form_error = {% if form.erros %}{{ form.errors.as_json|safe }}{% else %}false{% endif %};
|
||||
</script>
|
||||
{% vite_asset "app/login.js" %}
|
||||
{% endblock %}
|
||||
0
app/user/templates/user/register.html
Normal file
0
app/user/templates/user/register.html
Normal file
3
app/user/tests.py
Normal file
3
app/user/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
app/user/urls.py
Normal file
9
app/user/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import UserLoginView, RegisterView
|
||||
|
||||
app_name = "user"
|
||||
urlpatterns = [
|
||||
path("login/", UserLoginView.as_view(), name="login"),
|
||||
path("register/<uuid:token>/", RegisterView.as_view(), name="register"),
|
||||
]
|
||||
107
app/user/views.py
Normal file
107
app/user/views.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.views.generic import CreateView
|
||||
from django.contrib.auth import login
|
||||
from django.urls import reverse_lazy
|
||||
from django.db.models import Count, Sum, F, IntegerField
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import User, FriendRequest, Invitation
|
||||
from .forms import RegisterForm
|
||||
from .serializers import UserSerializer, FriendRequestSerializer, InvitationSerializer
|
||||
|
||||
|
||||
|
||||
class UserLoginView(LoginView):
|
||||
template_name = "user/login.html"
|
||||
fields = "__all__"
|
||||
redirect_authenticated_user = True
|
||||
|
||||
|
||||
class RegisterView(CreateView):
|
||||
template_name = "user/register.html"
|
||||
form_class = RegisterForm
|
||||
success_url = reverse_lazy("torrent:home")
|
||||
|
||||
invitation = None
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
self.invitation = Invitation.objects.get(token=self.kwargs.get("token"), user__isnull=True)
|
||||
return super().get_form(form_class)
|
||||
|
||||
def form_valid(self, form):
|
||||
r = super().form_valid(form)
|
||||
self.invitation.user = self.object
|
||||
self.invitation.save()
|
||||
login(self.request, self.object)
|
||||
return r
|
||||
|
||||
|
||||
class UserViewSet(mixins.RetrieveModelMixin,
|
||||
mixins.ListModelMixin,
|
||||
GenericViewSet):
|
||||
queryset = User.objects.all().annotate(
|
||||
count_torrent=Count("torrents") + Count("torrents_shares")
|
||||
)
|
||||
serializer_class = UserSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
||||
only_friends = self.request.query_params.get("only_friends", "false") == "true"
|
||||
if only_friends:
|
||||
qs = qs.filter(friends=self.request.user)
|
||||
|
||||
return qs
|
||||
|
||||
@action(methods=["get"], detail=True)
|
||||
def add_friend_request(self, request, pk):
|
||||
# receiver = User.objects.get(pk=pk)
|
||||
if not User.objects.filter(username__iexact=pk).exists():
|
||||
return Response({"success": False, "message": f"User '{pk}' doesn't exist"})
|
||||
|
||||
receiver = User.objects.get(username__iexact=pk)
|
||||
user: User = self.request.user
|
||||
if user.friends.filter(id=receiver.id).exists():
|
||||
# déjà dans les amis
|
||||
return Response({"success": False, "message": "Already friend"})
|
||||
elif FriendRequest.objects.filter(sender=user, receiver=receiver).exists():
|
||||
# déjà une demande en attente
|
||||
return Response({"success": False, "message": "Friend request Already sent"})
|
||||
elif FriendRequest.objects.filter(sender=receiver, receiver=user).exists():
|
||||
# friend request en cours, on accepte
|
||||
FriendRequest.objects.filter(sender=receiver, receiver=user).delete()
|
||||
user.friends.add(receiver)
|
||||
return Response({"success": True, "message": f"{receiver.username} added to your friend list"})
|
||||
else:
|
||||
# aucune demande en cours, on créer un friend request
|
||||
FriendRequest.objects.create(
|
||||
sender=user,
|
||||
receiver=receiver
|
||||
)
|
||||
return Response({"success": True, "message": "Request sent"})
|
||||
|
||||
@action(methods=["get"], detail=True)
|
||||
def remove_friend(self, request, pk):
|
||||
friend = User.objects.get(pk=pk)
|
||||
if self.request.user.friends.filter(pk=friend.pk).exists():
|
||||
self.request.user.friends.remove(friend)
|
||||
return Response({"success": True, "message": f"The friend {friend.username} successfully removed"})
|
||||
return Response({"success": False, "message": f"error"})
|
||||
|
||||
|
||||
class FriendRequestViewSet(mixins.ListModelMixin,
|
||||
mixins.DestroyModelMixin,
|
||||
GenericViewSet):
|
||||
queryset = FriendRequest.objects.all()
|
||||
serializer_class = FriendRequestSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
|
||||
qs = qs.filter(receiver=self.request.user)
|
||||
|
||||
return qs
|
||||
Reference in New Issue
Block a user