Reputation: 2416
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
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
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
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