Skip to content
65 changes: 65 additions & 0 deletions articles/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from articles.models import (
Article,
ArticlePDF,
ArticleVersion,
Discussion,
Reaction,
Review,
Expand All @@ -29,6 +30,7 @@
ArticleOut,
ArticlesListOut,
ArticleUpdateSchema,
ArticleVersionDiffOut,
CommunityArticleStatsResponse,
DateCount,
Message,
Expand Down Expand Up @@ -473,6 +475,15 @@ def update_article(
}

try:
# Save current state as a version before overwriting
latest_version = ArticleVersion.objects.filter(article=article).count()
ArticleVersion.objects.create(
article=article,
version=latest_version + 1,
title=article.title,
abstract=article.abstract,
)

# Update the article fields only if they are provided
article.title = details.payload.title
article.abstract = details.payload.abstract
Expand Down Expand Up @@ -551,6 +562,60 @@ def update_article(
return 500, {"message": "An unexpected error occurred. Please try again later."}


@router.get(
"/{article_id}/versions",
response={200: List[ArticleVersionDiffOut], codes_4xx: Message, codes_5xx: Message},
auth=OptionalJWTAuth,
summary="Get article version history with diffs",
)
def get_article_versions(request, article_id: int):
import difflib

try:
article = Article.objects.get(id=article_id)
except Article.DoesNotExist:
return 404, {"message": "Article not found."}

versions = list(ArticleVersion.objects.filter(article=article).order_by("version"))
if not versions:
return 200, []

# Build diff pairs: each version vs the next (or current article for the latest)
result = []
for i, version in enumerate(versions):
if i + 1 < len(versions):
next_title = versions[i + 1].title
next_abstract = versions[i + 1].abstract
else:
next_title = article.title
next_abstract = article.abstract

title_diff = list(
difflib.unified_diff(
version.title.splitlines(),
next_title.splitlines(),
lineterm="",
)
)
abstract_diff = list(
difflib.unified_diff(
version.abstract.splitlines(),
next_abstract.splitlines(),
lineterm="",
)
)
result.append(
ArticleVersionDiffOut(
version=version.version,
created_at=version.created_at,
title_diff=title_diff,
abstract_diff=abstract_diff,
)
)

return 200, result


@router.get(
"/",
response={
Expand Down
41 changes: 41 additions & 0 deletions articles/migrations/0034_articleversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("articles", "0033_alter_userflag_entity_type_alter_userflag_flag_type"),
]

operations = [
migrations.CreateModel(
name="ArticleVersion",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("version", models.PositiveIntegerField()),
("title", models.CharField(max_length=500)),
("abstract", models.TextField()),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"article",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="versions",
to="articles.article",
),
),
],
options={
"ordering": ["-version"],
},
),
]
16 changes: 16 additions & 0 deletions articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ def __str__(self):
return self.title


class ArticleVersion(models.Model):
article = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name="versions"
)
version = models.PositiveIntegerField()
title = models.CharField(max_length=500)
abstract = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ["-version"]

def __str__(self):
return f"Version {self.version} of {self.article.title}"


class ArticlePDF(models.Model):
def get_pdf_upload_path(instance, filename):
# Get file extension
Expand Down
14 changes: 14 additions & 0 deletions articles/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
AnonymousIdentity,
Article,
ArticlePDF,
ArticleVersion,
Discussion,
DiscussionComment,
DiscussionSubscription,
Expand Down Expand Up @@ -370,6 +371,19 @@ class ArticleFilters(Schema):
community_id: Optional[int] = None


class ArticleVersionOut(ModelSchema):
class Config:
model = ArticleVersion
model_fields = ["id", "version", "title", "abstract", "created_at"]


class ArticleVersionDiffOut(Schema):
version: int
created_at: datetime
title_diff: List[str]
abstract_diff: List[str]


"""
Article Reviews Schemas for serialization and validation
"""
Expand Down