Skip to content
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

Take care to cast dates with empty strings to NULL in model managers #253

Merged
merged 4 commits into from
Jul 15, 2019

Conversation

jeancochrane
Copy link
Contributor

Summary

While working on datamade/django-councilmatic-notifications#33, I noticed that if OCD BillActions or Events were saved with date values corresponding to empty strings, their date attributes would fail to be properly cast to DateTimeFields:

(councilmatic_models.BillAction.objects.annotate(test=Cast('date', models.DateTimeField()))
*** django.db.utils.DataError: invalid input syntax for type timestamp with time zone: ""

This PR adjusts the model managers implicated in django-councilmatic-notifications to appropriately cast empty strings to NULL.

Notes

@hancush pointed out that all of the date-related model attributes probably need to be handled in this way. That's a good point, but I'm not sure whether or not we should do it as part of this work. Since I don't have a good grasp of how pervasive this problem might be, I leave that decision up to others.

Testing instructions

I added tests to datamade/django-councilmatic-notifications#33 using these new null managers, so install this branch of django-councilmatic and run those tests to confirm tha they work.

@jeancochrane jeancochrane requested a review from hancush July 12, 2019 20:03
Case(
When(start_date='', then=None),
default='start_date',
output_field=models.CharField()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I thought that we might be able to set output_field to models.DateTimeField() and skip the enclosing Cast call altogether. Unfortunately, that doesn't work! Not entirely sure why, since output_field isn't really documented properly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've had trouble with that, too. boo!

Copy link
Member

@hancush hancush left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for taking this on, @jeancochrane. question re: pulling out repeat casting code inline!

models.DateTimeField()))
return super().get_queryset().annotate(
start_time=Cast(
Case(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use this same annotation in a handful of places, and it seems like we may want to use it more in the future, if we decide to handle all datetimes. what if we defined a mixin with a helper method and included it in the custom managers that need this functionality?

class CastToDateTimeMixin:
    def cast_to_datetime(self, field):
        return Cast(
            Case(
                When(**{field: '', 'then': None}),
                default=field,
                output_field=models.CharField()
            ),
            models.DateTimeField()
        )

class EventManager(CastToDateTimeMixin, models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(
            start_time=self.cast_to_datetime('start_date')
        )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a great idea! I'll make the change. What's the advantage of making it a mixin as opposed to a standalone function? Seems like it doesn't actually interface with the Manager methods or properties at all.

Case(
When(start_date='', then=None),
default='start_date',
output_field=models.CharField()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've had trouble with that, too. boo!

@hancush
Copy link
Member

hancush commented Jul 13, 2019 via email

@hancush
Copy link
Member

hancush commented Jul 13, 2019

p.s. could you use the new method in the existing membership manager, as well? 🙃

@jeancochrane jeancochrane force-pushed the feature/jfc/cast-dates-to-null branch from 2a917ec to 14506ba Compare July 15, 2019 16:18
@jeancochrane jeancochrane requested a review from hancush July 15, 2019 16:45
Copy link
Member

@hancush hancush left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you so much for taking this on, @jeancochrane!

i don't think order makes a difference right now, but it will if the casttodatetime mixin (or another one we add later) overrides or extends the manager base class sometime in the future, since python does class hierarchy right to left. would you mind swapping the order in the managers where we include the mixin?

other than that, this looks great to me.

@jeancochrane jeancochrane merged commit 0cde8a0 into 1.0 Jul 15, 2019
@jeancochrane jeancochrane deleted the feature/jfc/cast-dates-to-null branch July 15, 2019 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants