Repository: svthalia/concrexit

Issue:
Missing frontpage articles in API v2
### Describe the bug
When there are multiple frontpage articles that should be visible (such as currently on staging), `api/v2/announcements/frontpage-articles/` shows only one.



Discussion:
To me it feels like some sort of caching. When I add an article it doesn't show up in the API but if I then restart my server it immediately shows up. Slides seem to have the same issue.
That's exactly what I thought at first, but when I change the since date to yesterday on staging the article suddenly shows up?
It looks like the issue is the `VisibleObjectManager`. When the queryset get created, the datetime will be hardcoded into the query meaning that objects which are created after statup will be filtered out. I was looking at the [Now](https://docs.djangoproject.com/en/dev/ref/models/database-functions/#now) database function which should fix this issue but I'm not sure if it'll use the same timezone information as django does
I think it would work with timezones, based on
https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/tests/timezones/tests.py#L558
and 
https://code.djangoproject.com/ticket/31638
I was looking at replacing all `timezone.now()` calls in queries with [`Now()`](https://docs.djangoproject.com/en/dev/ref/models/database-functions/#now) to prevent these sort of issues in the future but then we would have an issue with stuff like this https://github.com/svthalia/concrexit/blob/b7d6159cbd8d023289e65ac3a6ec6237d0230de2/website/pizzas/models.py#L24 since there are no database functions to add something to a date. @se-bastiaan Do you think it's worth it to create our own database functions for that?
No, but maybe we can do something different? They do this in [the example](https://docs.djangoproject.com/en/3.2/topics/db/managers/#adding-extra-manager-methods).

```
class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(
            num_responses=Coalesce(models.Count("response"), 0)
        )
```

Not that we want to annotate, but maybe this will not permanently set the dates?
```
class OurManager(models.Manager):
    def current_only(self):
        return self.get_queryset().filter(
                        end__gt=timezone.now() - timezone.timedelta(hours=8),
                        start__lte=timezone.now() + timezone.timedelta(hours=8),
        )
```

That would still leave the same issue right? The problem is that the `queryset` gets instantiated once in the API view here https://github.com/svthalia/concrexit/blob/7e6b698c0a5ef3bc756803a2d3ab8ea14c0caf3a/website/announcements/api/v2/views.py#L40 and after that the times are hardcoded into the queryset https://github.com/svthalia/concrexit/blob/7e6b698c0a5ef3bc756803a2d3ab8ea14c0caf3a/website/announcements/models.py#L16-L26
Replacing these `timezone.now()` calls with the `Now()` database function would resolve that issue since in that case the timestamp will be evaluated when the actual data is queried.

I see that we don't use the `FoodEvent.current_objects` queryset in the API at the moment but we may want to do so in the future and in that case we would run into the same issue so I think we should remove all `timezone.now()` calls from at least the managers
Mmm and what about returning the queryset in the `get_queryset` function of the view? That one is called every time the queryset is requested and that way we would recreate it I believe.

For the articles using `Now()` is better anyways.

Resolving patch:
diff --git a/website/announcements/models.py b/website/announcements/models.py
--- a/website/announcements/models.py
+++ b/website/announcements/models.py
@@ -5,6 +5,7 @@
 )
 from django.db import models
 from django.db.models import CharField, Manager, Q
+from django.db.models.functions import Now
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from tinymce.models import HTMLField
@@ -19,8 +20,8 @@ def get_queryset(self):
             super()
             .get_queryset()
             .filter(
-                (Q(until__isnull=True) | Q(until__gt=timezone.now()))
-                & (Q(since__isnull=True) | Q(since__lte=timezone.now()))
+                (Q(until__isnull=True) | Q(until__gt=Now()))
+                & (Q(since__isnull=True) | Q(since__lte=Now()))
                 & ~(Q(since__isnull=True) & Q(until__isnull=True))
             )
         )


Test patch: