feat: add Resume model with JSONB fields and file upload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f5d753d441
commit
cc39c22e87
|
|
@ -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': '简历',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
@ -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']
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from .views import MyResumeView
|
||||||
|
|
||||||
urlpatterns = []
|
urlpatterns = [
|
||||||
|
path('me/', MyResumeView.as_view()),
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue