diff --git a/backend/leetcode/__init__.py b/backend/leetcode/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/leetcode/admin.py b/backend/leetcode/admin.py new file mode 100644 index 0000000..d1fcd78 --- /dev/null +++ b/backend/leetcode/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from .models import LeetCodeRecord +@admin.register(LeetCodeRecord) +class LeetCodeRecordAdmin(admin.ModelAdmin): + list_display = ("user","problem_id","title","difficulty","status","solved_at","updated_at") + search_fields = ("title","problem_id","user__username") + list_filter = ("difficulty","status") \ No newline at end of file diff --git a/backend/leetcode/models.py b/backend/leetcode/models.py new file mode 100644 index 0000000..07395d4 --- /dev/null +++ b/backend/leetcode/models.py @@ -0,0 +1,21 @@ +from django.conf import settings +from django.db import models + +class LeetCodeRecord(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="leetcode_records") + problem_id = models.CharField(max_length=64) # 如 1, 2, 3 或 slug + title = models.CharField(max_length=255) + url = models.URLField(blank=True) + difficulty = models.CharField(max_length=16, choices=[("easy","easy"),("medium","medium"),("hard","hard")]) + status = models.CharField(max_length=16, choices=[("solved","solved"),("attempted","attempted")], default="solved") + language = models.CharField(max_length=64, blank=True) # Python/Java/C++ 等 + tags = models.JSONField(default=list, blank=True) # sqlite 可用 JSONField + time_spent_min = models.PositiveIntegerField(default=0) + notes = models.TextField(blank=True) + solved_at = models.DateTimeField(null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ("user", "problem_id") + ordering = ["-solved_at", "-updated_at"] diff --git a/backend/leetcode/urls.py b/backend/leetcode/urls.py new file mode 100644 index 0000000..4dcb11c --- /dev/null +++ b/backend/leetcode/urls.py @@ -0,0 +1,19 @@ +from rest_framework import viewsets, permissions, filters +from django_filters.rest_framework import DjangoFilterBackend +from .models import LeetCodeRecord +from .serializers import LeetCodeRecordSerializer + +class LeetCodeRecordViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = LeetCodeRecordSerializer + + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_fields = ["difficulty","status"] + search_fields = ["title","problem_id","tags"] + ordering_fields = ["solved_at","updated_at","time_spent_min"] + + def get_queryset(self): + return LeetCodeRecord.objects.filter(user=self.request.user) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) diff --git a/backend/leetcode/view.py b/backend/leetcode/view.py new file mode 100644 index 0000000..4dcb11c --- /dev/null +++ b/backend/leetcode/view.py @@ -0,0 +1,19 @@ +from rest_framework import viewsets, permissions, filters +from django_filters.rest_framework import DjangoFilterBackend +from .models import LeetCodeRecord +from .serializers import LeetCodeRecordSerializer + +class LeetCodeRecordViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = LeetCodeRecordSerializer + + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_fields = ["difficulty","status"] + search_fields = ["title","problem_id","tags"] + ordering_fields = ["solved_at","updated_at","time_spent_min"] + + def get_queryset(self): + return LeetCodeRecord.objects.filter(user=self.request.user) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) diff --git a/backend/project/settings.py b/backend/project/settings.py index f2b3e94..e83ca4b 100644 --- a/backend/project/settings.py +++ b/backend/project/settings.py @@ -49,14 +49,25 @@ ] REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.TokenAuthentication", + "rest_framework.authentication.SessionAuthentication", ], + "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], -} + "DEFAULT_FILTER_BACKENDS": [ + "django_filters.rest_framework.DjangoFilterBackend", + "rest_framework.filters.SearchFilter", + "rest_framework.filters.OrderingFilter", + ], + + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 20, +} ALLOWED_HOSTS = ["127.0.0.1", "localhost"] MEDIA_URL = "/media/"