diff --git a/offer_backend/apps/resumes/migrations/0001_initial.py b/offer_backend/apps/resumes/migrations/0001_initial.py new file mode 100644 index 0000000..a3a5116 --- /dev/null +++ b/offer_backend/apps/resumes/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.20 on 2026-03-24 09:43 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Resume', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, verbose_name='姓名')), + ('gender', models.CharField(blank=True, choices=[('male', '男'), ('female', '女'), ('other', '其他')], max_length=10)), + ('birthday', models.DateField(blank=True, null=True)), + ('education', models.JSONField(default=list, verbose_name='教育经历')), + ('experience', models.JSONField(default=list, verbose_name='工作经历')), + ('attachment', models.FileField(blank=True, null=True, upload_to='resumes/')), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='resume', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': '简历', + }, + ), + ] diff --git a/offer_backend/apps/resumes/migrations/__init__.py b/offer_backend/apps/resumes/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/offer_backend/apps/resumes/models.py b/offer_backend/apps/resumes/models.py new file mode 100644 index 0000000..0189617 --- /dev/null +++ b/offer_backend/apps/resumes/models.py @@ -0,0 +1,32 @@ +from django.db import models +from django.conf import settings + + +class Resume(models.Model): + GENDER_CHOICES = [('male', '男'), ('female', '女'), ('other', '其他')] + user = models.OneToOneField( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name='resume' + ) + name = models.CharField(max_length=50, verbose_name='姓名') + gender = models.CharField(max_length=10, choices=GENDER_CHOICES, blank=True) + birthday = models.DateField(null=True, blank=True) + education = models.JSONField(default=list, verbose_name='教育经历') + experience = models.JSONField(default=list, verbose_name='工作经历') + attachment = models.FileField(upload_to='resumes/', null=True, blank=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = '简历' + + def to_snapshot(self): + """序列化为投递快照,与主表解耦""" + return { + 'name': self.name, + 'gender': self.gender, + 'birthday': str(self.birthday) if self.birthday else None, + 'education': self.education, + 'experience': self.experience, + 'attachment_url': self.attachment.url if self.attachment else None, + } diff --git a/offer_backend/apps/resumes/serializers.py b/offer_backend/apps/resumes/serializers.py new file mode 100644 index 0000000..62466d5 --- /dev/null +++ b/offer_backend/apps/resumes/serializers.py @@ -0,0 +1,10 @@ +from rest_framework import serializers +from .models import Resume + + +class ResumeSerializer(serializers.ModelSerializer): + class Meta: + model = Resume + fields = ['id', 'name', 'gender', 'birthday', 'education', + 'experience', 'attachment', 'updated_at'] + read_only_fields = ['updated_at'] diff --git a/offer_backend/apps/resumes/tests/__init__.py b/offer_backend/apps/resumes/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/offer_backend/apps/resumes/tests/test_resumes.py b/offer_backend/apps/resumes/tests/test_resumes.py new file mode 100644 index 0000000..6fe8ddd --- /dev/null +++ b/offer_backend/apps/resumes/tests/test_resumes.py @@ -0,0 +1,29 @@ +import pytest +from django.contrib.auth import get_user_model +from apps.resumes.models import Resume + +User = get_user_model() + + +@pytest.fixture +def seeker(): + return User.objects.create_user(username='seeker1', password='pass', role='seeker') + + +@pytest.mark.django_db +class TestResumeModel: + def test_create_resume(self, seeker): + resume = Resume.objects.create( + user=seeker, + name='张三', + gender='male', + education=[{'school': '北京大学', 'degree': '本科', 'major': '计算机'}], + experience=[{'company': 'ABC公司', 'position': '工程师', 'duration': '2年'}], + ) + assert resume.name == '张三' + assert len(resume.education) == 1 + assert len(resume.experience) == 1 + + def test_seeker_has_one_resume(self, seeker): + Resume.objects.create(user=seeker, name='张三') + assert Resume.objects.filter(user=seeker).count() == 1 diff --git a/offer_backend/apps/resumes/urls.py b/offer_backend/apps/resumes/urls.py index e39cb2c..a2eb767 100644 --- a/offer_backend/apps/resumes/urls.py +++ b/offer_backend/apps/resumes/urls.py @@ -1,3 +1,6 @@ from django.urls import path +from .views import MyResumeView -urlpatterns = [] +urlpatterns = [ + path('me/', MyResumeView.as_view()), +] diff --git a/offer_backend/apps/resumes/views.py b/offer_backend/apps/resumes/views.py new file mode 100644 index 0000000..d6396a4 --- /dev/null +++ b/offer_backend/apps/resumes/views.py @@ -0,0 +1,17 @@ +from rest_framework import generics +from .models import Resume +from .serializers import ResumeSerializer +from apps.accounts.permissions import IsSeeker + + +class MyResumeView(generics.RetrieveUpdateAPIView): + """求职者获取/更新自己的简历(不存在则自动创建)""" + serializer_class = ResumeSerializer + permission_classes = [IsSeeker] + + def get_object(self): + resume, _ = Resume.objects.get_or_create( + user=self.request.user, + defaults={'name': self.request.user.username} + ) + return resume