Introduction
Quite recently I came across a tweet by Florin Pop which talked about tracking your minor and major achievements using a timeline. It was an interesting idea.. how come I never thought about this. Thus, inspired, I set on to start creating my own version. In this article, you will see my journey from inspiration to creation.
Tech Stack
What I wanted at the end was very clear, only the tech I needed to use was to be figured out. I chose Django as a backend .. because why not.. it provided easy to manage model-view-control architecture and out of the box admin panel and front-end was going to be simple Html and CSS, I am not a fancy man.. simple is fine with me.
Starting out
I started out by creating a simple Django project and then creating a timeline app. I planned to add more stuff to it moving forward so creating a separate app was good idea. further, I added card model with simply keeping heading, body, date as fields if anything else is ever needed like tags.. (which I plan to add later) can be added easily.
class Card(models.Model):
"""
In timeline: Each achievement is shown in card
"""
heading = models.CharField(max_length=55, default="I forgot to add heading..", blank=False, null=False)
body = models.TextField(max_length=999, default="", null=True, blank=True)
date = models.DateField(default=timezone.now, null=False, blank=False)
def __str__(self) -> str:
return f"{self.heading}.. on {self.date}"
once the model was done.. it was time to create some view to handle some behind the scene stuff. I wanted a timeline in which all the cards (i.e. achievements) are separated by years.. thus I needed to handle this.
For view, I went with class based generic view django.views.generic.View
and defined my own request handling methods.. since I figured I might be needing custom things in future.
class CardView(View):
template_name = 'timeline/card_list_view.html'
def get(self, request):
cards = Card.objects.all().order_by('-date')
return render(request, template_name=self.template_name, context={'timeline_data':cards})
For my first approach, I simply tried rendering all cards to front.. which I soon figured is not gonna work. Since there is no way in Django(at least I couldn't find) that I can query the database in such a way that can give me cards separated by years. Hence, I added my own logic to handle this.
class CardView(View):
template_name = 'timeline/card_list_view.html'
def get(self, request):
cards = Card.objects.all().order_by('-date').values()
qs = defaultdict(list)
for query in cards:
card_year = query['date'].year
qs[card_year] += [query]
qs = dict(qs)
return render(request, template_name=self.template_name, context={'timeline_data':qs})
I used defaultdict
, very helpful tool.. saved me some if-else
and try/except
. If you are wondering what I am doing here - first I am getting all the cards in newest to oldest order fashion then .values()
is converting it to list of dictionaries. I then created a defaultdict object.. in which each item has list
as default value and added all the cards of the same year in a key-value pair. That's it.. that's literally the depth of backend code.
Moving forward, I am little too lazy to create my own frontend thus I looked around here and there and found a great timeline template just as the way I wanted - simple yet powerful ( check here ).
There was a problem.. it was using different css class for alternate cards left for odd ones and right for even. So, for me to use this I needed a way to use
- counters in Django templates, and
- find a way to if they are even or odd..since we cannot use simple %2 there.
Thus I did some looking around and found not one but two unexpected things .. together solving my problem. First is forloop.counter
, as the name suggests.. it is a counter for for
loops in django.. and second one divisibleby
filter (literally Django has everything). Finally, my front end was looking something like this.. (I have trimmed irrelevant code)
<div class="timeline-continue">
{% for year, card_list in timeline_data.items %}
{% for card in card_list %}
{% if forloop.counter|divisibleby:2 %}
{% include 'timeline/snippets/right_card.html' with heading=card.heading date=card.date body=card.body %}
{% else %}
{% include 'timeline/snippets/left_card.html' with heading=card.heading date=card.date body=card.body %}
{% endif %}
{% endfor %}
{% include 'timeline/snippets/year_circle.html' with year=year %}
{% endfor %}
</div>
and yes, I used snippets to follow DRY principle.. with left and right card snippets looking like this. left card
<div class="row timeline-right">
<div class="col-md-6">
<p class="timeline-date">
{{date|date:"F "}}
</p>
</div>
<div class="col-md-6">
<div class="timeline-box">
<div class="timeline-icon">
<i class="fa fa-gift"></i>
</div>
<div class="timeline-text">
<center>
<h3>{{heading|title }}</h3>
<p>{{body}}</p>
</center>
</div>
</div>
</div>
</div>
right card
<div class="row timeline-left">
<div class="col-md-6 d-md-none d-block">
<p class="timeline-date">
{{date|date:"F"}}
</p>
</div>
<div class="col-md-6">
<div class="timeline-box">
<div class="timeline-icon d-md-none d-block">
<i class="fa fa-business-time"></i>
</div>
<div class="timeline-text">
<center>
<h3>{{heading|title }}</h3>
<p>{{body}}</p>
</center>
</div>
<div class="timeline-icon d-md-block d-none">
<i class="fa fa-business-time"></i>
</div>
</div>
</div>
<div class="col-md-6 d-md-block d-none">
<p class="timeline-date">
{{date|date:"F"}}
</p>
</div>
</div>
Honestly, it was it. It was just a matter of creating some cards in the database to check the final product. Which I believe came out well. You can see this final product here
Source Code
You can look at the complete code base here.
credits : Article Cover Photo by Florian Klauer on Unsplash