Skip to content

Commit 66743de

Browse files
committed
Merge branch 'main' of github.com:lqez/python.or.kr-wip
2 parents 2f9d035 + 9f0260d commit 66743de

20 files changed

+2169
-305
lines changed

.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OPENAI_API_KEY=sk-proj-xxxxx
2+
GEMINI_API_KEY=asdasd

.github/workflows/deploy.yml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
name: pk web site backend deployment
22

33
on:
4-
workflow_run:
5-
workflows: ["pk web site backend testing"]
6-
types:
7-
- completed
4+
push:
5+
branches:
6+
- main # main 브랜치에 푸시될 때만 실행
87

98
jobs:
109
deploy:
1110
runs-on: ubuntu-latest
12-
if: ${{ github.event.workflow_run.conclusion == 'success' && github.ref == 'refs/heads/main' }}
11+
1312
steps:
1413
- name: executing remote ssh commands for update
1514
uses: appleboy/[email protected]
1615
with:
1716
host: ${{ secrets.HOSTNAME }}
1817
username: ${{ secrets.USERNAME }}
1918
key: ${{ secrets.KEY }}
20-
script:
19+
script: |
2120
cd python.or.kr && git pull
2221
2322
- name: executing remote ssh commands for deployment
@@ -26,6 +25,5 @@ jobs:
2625
host: ${{ secrets.HOSTNAME }}
2726
username: ${{ secrets.USERNAME }}
2827
key: ${{ secrets.KEY }}
29-
script:
28+
script: |
3029
cd python.or.kr && bash deploy_prod.sh
31-

docker-compose.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ services:
1515
- ./pythonkr_backend:/app/pythonkr_backend
1616
ports:
1717
- "8080:8080"
18+
env_file:
19+
- .env
1820
depends_on:
1921
- db
2022
environment:
@@ -23,4 +25,4 @@ services:
2325
POSTGRES_PASSWORD: pktesting
2426

2527
volumes:
26-
postgres_data:
28+
postgres_data:

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@ description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.12"
77
dependencies = [
8+
"beautifulsoup4>=4.13.3",
89
"django>=5.1.7",
910
"django-tailwind[reload]>=3.8.0",
1011
"gunicorn>=23.0.0",
1112
"httpx>=0.28.1",
13+
"llm>=0.24.2",
14+
"llm-gemini>=0.18.1",
15+
"lxml>=5.3.2",
1216
"markdown>=3.7",
1317
"psycopg[binary]>=3.2.5",
18+
"pydantic-ai[logfire]>=0.2.6",
19+
"readtime>=3.0.0",
20+
"requests>=2.32.3",
21+
"tiktoken>=0.9.0",
1422
"wagtail>=6.4.1",
1523
"wagtail-bakery>=0.8.0",
1624
]

pythonkr_backend/curation/__init__.py

Whitespace-only changes.

pythonkr_backend/curation/admin.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from django.contrib import admin, messages
2+
from .models import Article, Category # Or combine imports
3+
4+
5+
@admin.register(Category)
6+
class CategoryAdmin(admin.ModelAdmin):
7+
list_display = ('name', 'slug')
8+
search_fields = ('name',)
9+
prepopulated_fields = {'slug': ('name',)} # Auto-populate slug from name
10+
11+
12+
@admin.action(description="Fetch content, summarize, and translate selected articles")
13+
def summarize_selected_articles(modeladmin, request, queryset):
14+
success_count = 0
15+
errors = []
16+
17+
for article in queryset:
18+
result = article.fetch_and_summarize()
19+
if result.startswith("Error"):
20+
errors.append(f"{article.url}: {result}")
21+
else:
22+
success_count += 1
23+
24+
if success_count > 0:
25+
modeladmin.message_user(
26+
request,
27+
f"Successfully processed {success_count} article(s) (fetch, summarize, translate).",
28+
messages.SUCCESS
29+
)
30+
31+
if errors:
32+
error_message = "Errors encountered:\n" + "\n".join(errors)
33+
modeladmin.message_user(request, error_message, messages.WARNING)
34+
35+
36+
37+
38+
@admin.register(Article)
39+
class ArticleAdmin(admin.ModelAdmin):
40+
list_display = ('url', 'title', 'display_categories', 'summary_preview', 'summary_ko_preview', 'reading_time_minutes', 'updated_at', 'created_at')
41+
list_filter = ('categories', 'created_at', 'updated_at')
42+
search_fields = ('url', 'title', 'summary', 'summary_ko', 'categories__name')
43+
readonly_fields = ('created_at', 'updated_at', 'summary', 'summary_ko', 'reading_time_minutes')
44+
actions = [summarize_selected_articles]
45+
filter_horizontal = ('categories',)
46+
47+
fieldsets = (
48+
('Article Information', {
49+
'fields': ('url', 'title', 'categories')
50+
}),
51+
('Generated Content', {
52+
'fields': ('summary', 'summary_ko', 'reading_time_minutes'),
53+
'classes': ('collapse',)
54+
}),
55+
('Metadata', {
56+
'fields': ('created_at', 'updated_at'),
57+
'classes': ('collapse',)
58+
}),
59+
)
60+
61+
@admin.display(description='Categories')
62+
def display_categories(self, obj):
63+
"""Displays categories as a comma-separated string in the list view."""
64+
if obj.categories.exists():
65+
return ", ".join([category.name for category in obj.categories.all()])
66+
return '-' # Or None, or empty string
67+
68+
def get_readonly_fields(self, request, obj=None):
69+
# Make 'categories' always read-only as it's set by the LLM
70+
readonly = list(super().get_readonly_fields(request, obj))
71+
if 'categories' not in readonly:
72+
readonly.append('categories')
73+
return readonly
74+
75+
@admin.display(description='Summary Preview')
76+
def summary_preview(self, obj):
77+
if obj.summary:
78+
preview = obj.summary[:100]
79+
return f"{preview}..." if len(obj.summary) > 100 else preview
80+
return "No summary available"
81+
82+
@admin.display(description='Korean Summary Preview')
83+
def summary_ko_preview(self, obj):
84+
if obj.summary_ko:
85+
if obj.summary_ko.startswith("Translation Error"): return obj.summary_ko
86+
preview = obj.summary_ko[:50]
87+
return f"{preview}..." if len(obj.summary_ko) > 50 else preview
88+
return "No Korean summary"

pythonkr_backend/curation/apps.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.apps import AppConfig
2+
3+
4+
class CurationConfig(AppConfig):
5+
default_auto_field = 'django.db.models.BigAutoField'
6+
name = 'curation'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.2 on 2025-04-20 05:42
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = [
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Article',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('url', models.URLField(help_text='The unique URL of the article.', max_length=2048, unique=True)),
19+
('title', models.CharField(blank=True, help_text='Article title (can be fetched automatically or entered manually).', max_length=512)),
20+
('summary', models.TextField(blank=True, help_text='AI-generated summary of the article.')),
21+
('created_at', models.DateTimeField(auto_now_add=True)),
22+
('updated_at', models.DateTimeField(auto_now=True)),
23+
],
24+
),
25+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2 on 2025-04-20 06:20
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('curation', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='article',
15+
name='reading_time_minutes',
16+
field=models.PositiveIntegerField(blank=True, help_text='Estimated reading time in minutes.', null=True),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2 on 2025-04-20 06:23
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('curation', '0002_article_reading_time_minutes'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='article',
15+
name='summary_ko',
16+
field=models.TextField(blank=True, help_text='Korean translation of the summary (via OpenAI).'),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2 on 2025-04-20 06:34
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('curation', '0003_article_summary_ko'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='article',
15+
name='reading_time_minutes',
16+
field=models.PositiveIntegerField(blank=True, help_text='Estimated reading time in minutes (based on full article content).', null=True),
17+
),
18+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.2 on 2025-04-20 07:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('curation', '0004_alter_article_reading_time_minutes'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='Category',
15+
fields=[
16+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('name', models.CharField(help_text="The name of the category (e.g., 'Web Development', 'LLM').", max_length=100, unique=True)),
18+
('slug', models.SlugField(blank=True, help_text='A URL-friendly slug for the category.', max_length=100, unique=True)),
19+
],
20+
options={
21+
'verbose_name_plural': 'Categories',
22+
'ordering': ['name'],
23+
},
24+
),
25+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2 on 2025-04-20 07:25
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('curation', '0005_category'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='article',
15+
name='categories',
16+
field=models.ManyToManyField(blank=True, help_text='Select one or more categories for this article.', related_name='articles', to='curation.category'),
17+
),
18+
]

pythonkr_backend/curation/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)