Tomas Jacobsen
Tomas Jacobsen

Reputation: 2416

Determine if entry is visible by startdate and enddate

I have a model with three fields to determine if a text should be visible in the template.

If the boolean field is checked, it would override the two datefields.

If the boolean field is not checked, it should only return True if the startdate is in the past, and if the enddate is set, it should only return true if the date is set in the future.

How can I do this?

Here is what I have tried, but is not working like I want:

class Entry(models.Model):
    name = models.CharField(max_length=200)

    visible = models.BooleanField(default=False, help_text="If checked, the text will be visible, even if the datefield is set")
    visible_start = models.DateTimeField(help_text="Visible from", blank=True, null=True)
    visible_end = models.DateTimeField(help_text="Visible to", blank=True, null=True)

    def is_visible(self):
        now = timezone.now()

        if not self.visible:
            if self.visible_start and now < self.visible_start:
                return False

            if self.visible_end and now > self.visible_end:  
                return False            

        return True

Upvotes: 0

Views: 84

Answers (2)

allcaps
allcaps

Reputation: 11248

Start and stop have null=True. Therefore you can check: Null < now and Null > Now (always False). If start and stop are within the now criteria, than the code below will return True.

def is_visible(self):
    now = datetime.now()

    if self.is_visible:
        # is_visible field is checked.
        return True

    #elif self.visible_start < now and self.visible_stop == Null:
    #    return True

    elif self.visible_start < now and self.visible_stop > now:
        # Start is before now and stop is after now.
        return True

    else:
        # All other options
        return False

UPDATE

I added this to show when to use filters, method and when a manager.

It looks like you want to do something like this:

# views.py
objects = Entry.objects.all()

# template.html
{% for obj in objects %}
    {% if obj.is_visible %}
        {{ obj.title }}
    {% endif %}
{% endfor %}

Don't do it! This will loop all entries (can be many). It's a performance nightmare! Filter the entries first. Like this:

# views.py
now = datetime.now()    
objs0 = Entry.objects.filter(visible=True)
objs1 = Entry.objects.filter(visible_start__lte=now, visible_stop__gt=now)
objects = objs0 | objs1

# template.html
{% for obj in objects %}
    {{ obj.title }}
{% endfor %}

This will only loop threw relevant objects. If this list occurs multiple times in your project, and you notice writing above view multiple times, than consider a model manager. Like suggested by @HeddeVanDerHeide. After you've written a manager you can get all visible objects:

objects = Entry.visible_objects.all()

Why not filter on the model method directly?

objects = Entry.objects.filter(is_visible=True)

Because Django doesn't work that way. Queries are executed against the database and the database doesn't know about methods. But what good is a model method? Use a model method if you want to display differences between entries:

# models.py
def is_important(self):
    now = datetime.now()
    if self.start < now and self.stop > now:
        return True
    else:
        return False

# views.py
objects = Entries.objects.all()

# template.html
{% for obj in objects %}
    <p{% if obj.is_important %} class="highlight"{% endif %}>{{ obj.title }}</p>
{% endfor %}

And when you want to show a boolean in the admin list display, than model method makes this possible:

class EntryAdmin(admin.ModelAdmin):
    list_display = ('title', 'is_visible')

From the documentation:

Define custom methods on a model to add custom “row-level” functionality to your objects. Whereas Manager methods are intended to do “table-wide” things.

Upvotes: 1

Hedde van der Heide
Hedde van der Heide

Reputation: 22459

You could implement a manager, e.g. (untested):

class VisibleEntryManager(models.Manager):
    def get_query_set(self):
        now = datetime.now()
        return super(VisibleEntryManager, self).get_query_set()\
               .filter(visible=True)\
               .filter(visible_start__gte=now, visible_end__lte=now) # Or use Q objects

class Entry(models.Model):
    # other stuff
    visible_objects = VisibleEntryManager()

Edit: the admin uses the default manager (by calling _default_manager on the Model class). If you want to defer admin objects you can do it like this:

class EntryAdmin(admin.ModelAdmin):
    # other stuff

    def queryset(self, request):
        if request.user.is_superuser or request.user.is_staff:  # example rule
            qs = self.model.objects.all()  # could be admin_objects if you want to set 
                                           # .objects as the default filtered option on 
                                           # the Entry model.
            ordering = self.get_ordering(request)
            if ordering:
                qs = qs.order_by(*ordering)
            return qs
        return super(ProductAdmin, self).queryset(request)

Upvotes: 3

Related Questions