Skip to content

Fix issue #184: Add timestamps to models V2 #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 4.2.8 on 2025-02-03 10:26

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("photo", "0004_alter_contest_prize"),
]

operations = [
migrations.AddField(
model_name="collection",
name="created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="collection",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="contest",
name="created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="contest",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="contestsubmission",
name="created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="contestsubmission",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="picture",
name="created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="picture",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="picturecomment",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="user",
name="created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="user",
name="updated_at",
field=models.DateTimeField(blank=True, null=True),
),
]
11 changes: 11 additions & 0 deletions photo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def create_superuser(self, email, password=None, **kwargs):

class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=False)
created_at = models.DateTimeField(null=True, blank=True)
updated_at = models.DateTimeField(null=True, blank=True)
objects = SoftDeleteManager()
all_objects = models.Manager()

Expand All @@ -60,6 +62,12 @@ def restore(self):
self.is_deleted = False
self.save()

def save(self, *args, **kwargs):
if not self.created_at:
self.created_at = timezone.now()
self.updated_at = timezone.now()
super().save(*args, **kwargs)

class Meta:
abstract = True

Expand Down Expand Up @@ -106,6 +114,9 @@ def validate_profile_picture(self):

def save(self, *args, **kwargs):
self.validate_profile_picture()
if not self.created_at:
self.created_at = timezone.now()
self.updated_at = timezone.now()
super(User, self).save(*args, **kwargs)


Expand Down
153 changes: 153 additions & 0 deletions photo/tests/test_database/test_timestamps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from django.test import TransactionTestCase
from django.utils import timezone
from datetime import timedelta
import time

from photo.models import Collection, Contest, ContestSubmission, Picture, PictureComment, User
from photo.tests.factories import (
CollectionFactory,
ContestFactory,
ContestSubmissionFactory,
PictureFactory,
PictureCommentFactory,
UserFactory,
)


class TimestampFieldsTest(TransactionTestCase):
def setUp(self):
self.user = UserFactory()
self.picture = PictureFactory(user=self.user)
self.collection = CollectionFactory(user=self.user)
self.contest = ContestFactory(created_by=self.user)
self.submission = ContestSubmissionFactory(
contest=self.contest,
picture=self.picture
)
self.comment = PictureCommentFactory(
user=self.user,
picture=self.picture
)

def test_created_at_on_creation(self):
"""Test that created_at is set on object creation"""
now = timezone.now()

# Test for each model
self.assertIsNotNone(self.user.created_at)
self.assertLess(self.user.created_at, now)

self.assertIsNotNone(self.picture.created_at)
self.assertLess(self.picture.created_at, now)

self.assertIsNotNone(self.collection.created_at)
self.assertLess(self.collection.created_at, now)

self.assertIsNotNone(self.contest.created_at)
self.assertLess(self.contest.created_at, now)

self.assertIsNotNone(self.submission.created_at)
self.assertLess(self.submission.created_at, now)

self.assertIsNotNone(self.comment.created_at)
self.assertLess(self.comment.created_at, now)

def test_updated_at_on_creation(self):
"""Test that updated_at is set on object creation"""
now = timezone.now()

# Test for each model
self.assertIsNotNone(self.user.updated_at)
self.assertLess(self.user.updated_at, now)

self.assertIsNotNone(self.picture.updated_at)
self.assertLess(self.picture.updated_at, now)

self.assertIsNotNone(self.collection.updated_at)
self.assertLess(self.collection.updated_at, now)

self.assertIsNotNone(self.contest.updated_at)
self.assertLess(self.contest.updated_at, now)

self.assertIsNotNone(self.submission.updated_at)
self.assertLess(self.submission.updated_at, now)

self.assertIsNotNone(self.comment.updated_at)
self.assertLess(self.comment.updated_at, now)

def test_updated_at_on_update(self):
"""Test that updated_at is updated when object is modified"""
# Store initial timestamps
user_updated = self.user.updated_at
picture_updated = self.picture.updated_at
collection_updated = self.collection.updated_at
contest_updated = self.contest.updated_at
submission_updated = self.submission.updated_at
comment_updated = self.comment.updated_at

# Wait a bit to ensure timestamps will be different
time.sleep(0.01) # 10 milliseconds

# Update each object
self.user.name_first = "Updated"
self.user.save()

self.picture.name = "Updated Picture"
self.picture.save()

self.collection.name = "Updated Collection"
self.collection.save()

self.contest.title = "Updated Contest"
self.contest.save()

self.submission.submission_date = timezone.now()
self.submission.save()

self.comment.text = "Updated Comment"
self.comment.save()

# Verify updated_at was changed
self.assertGreater(self.user.updated_at, user_updated)
self.assertGreater(self.picture.updated_at, picture_updated)
self.assertGreater(self.collection.updated_at, collection_updated)
self.assertGreater(self.contest.updated_at, contest_updated)
self.assertGreater(self.submission.updated_at, submission_updated)
self.assertGreater(self.comment.updated_at, comment_updated)

def test_created_at_unchanged_on_update(self):
"""Test that created_at remains unchanged when object is modified"""
# Store initial timestamps
user_created = self.user.created_at
picture_created = self.picture.created_at
collection_created = self.collection.created_at
contest_created = self.contest.created_at
submission_created = self.submission.created_at
comment_created = self.comment.created_at

# Update each object
self.user.name_first = "Updated"
self.user.save()

self.picture.name = "Updated Picture"
self.picture.save()

self.collection.name = "Updated Collection"
self.collection.save()

self.contest.title = "Updated Contest"
self.contest.save()

self.submission.submission_date = timezone.now()
self.submission.save()

self.comment.text = "Updated Comment"
self.comment.save()

# Verify created_at was not changed
self.assertEqual(self.user.created_at, user_created)
self.assertEqual(self.picture.created_at, picture_created)
self.assertEqual(self.collection.created_at, collection_created)
self.assertEqual(self.contest.created_at, contest_created)
self.assertEqual(self.submission.created_at, submission_created)
self.assertEqual(self.comment.created_at, comment_created)
9 changes: 9 additions & 0 deletions photo/tests/test_queries/graphql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
pictures {
id
}
created_at
updated_at
}
}
"""
Expand Down Expand Up @@ -55,6 +57,8 @@
id
}
status
created_at
updated_at
}
}
"""
Expand Down Expand Up @@ -131,6 +135,8 @@
votes {
email
}
created_at
updated_at
}
}
"""
Expand Down Expand Up @@ -169,6 +175,8 @@
likes {
email
}
created_at
updated_at
}
}
"""
Expand Down Expand Up @@ -200,6 +208,7 @@
}
text
created_at
updated_at
}
}
"""
Expand Down
10 changes: 3 additions & 7 deletions photo/tests/test_queries/test_picture_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,9 @@ def test_query_success(self):

self.assertEqual(result.errors, None)
self.assertEqual(len(result.data["picture_comments"]), self.batch_size)
self.assertEqual(
sorted(
[key for key in result.data["picture_comments"][0].keys()]
+ ["is_deleted"]
),
sorted([field.name for field in PictureComment._meta.fields]),
)
expected_fields = sorted([field.name for field in PictureComment._meta.fields])
actual_fields = sorted([key for key in result.data["picture_comments"][0].keys()] + ["is_deleted"])
self.assertEqual(actual_fields, expected_fields)

def test_filter_by_id(self):
picture_comment = PictureCommentFactory.create()
Expand Down
11 changes: 11 additions & 0 deletions photo/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class UserType:
profile_picture: "PictureType"
profile_picture_updated_at: strawberry.auto
user_handle: str
created_at: strawberry.auto
updated_at: strawberry.auto


@strawberry.django.type(Picture)
Expand All @@ -32,6 +34,8 @@ class PictureType:
name: str
file: str
likes: List[UserType]
created_at: strawberry.auto
updated_at: strawberry.auto


@strawberry.django.type(PictureComment)
Expand All @@ -41,6 +45,7 @@ class PictureCommentType:
picture: "PictureType"
text: str
created_at: strawberry.auto
updated_at: strawberry.auto


@strawberry.django.type(Collection)
Expand All @@ -49,6 +54,8 @@ class CollectionType:
name: str
user: "UserType"
pictures: List[PictureType]
created_at: strawberry.auto
updated_at: strawberry.auto


@strawberry.django.type(Contest)
Expand All @@ -67,6 +74,8 @@ class ContestType:
winners: List[UserType]
created_by: "UserType"
status: str
created_at: strawberry.auto
updated_at: strawberry.auto

@strawberry.field
def status(self) -> str:
Expand All @@ -92,6 +101,8 @@ class ContestSubmissionType:
picture: PictureType
submission_date: strawberry.auto
votes: List[UserType]
created_at: strawberry.auto
updated_at: strawberry.auto


@strawberry.type
Expand Down