blog

Add necessary changes for RSS feed access

I've updated the index template to provide a button for the RSS feed. That links to an URL that automatically renders the RSS feed, collects the updates, and outputs a usable XML file.

Author
Maarten 'Vngngdn' Vangeneugden
Date
April 9, 2018, 8:28 p.m.
Hash
409038fe4f555d356da53ed03a3d9b4532689745
Parent
49d0aeaffe4cf708a597a2408cfe94e2de26ad54
Modified files
TODO.org
admin.py
models.py
templates/blog/feed.rss
templates/blog/index.html
urls.py
views.py

TODO.org

5 additions and 0 deletions.

View changes Hide changes
1
1
  annoying bug where the layout of the input fields cannot be reverted to the
2
2
  browser default, despite using a class for *exactly that fucking reason*. I'm
3
3
  currently too fed up to care about fixing that shit right now.
4
4
+
5
     that comments are not checked for identity, and, unless verified by a
+
6
     trustworthy source, cannot be seen as being an actual statement from the commenter.
+
7
- Notification regarding languages :: Add a warning that comments show up in all
+
8
     language versions, regardless of the active language during submission.
+
9

admin.py

2 additions and 1 deletion.

View changes Hide changes
1
1
2
2
from .models import Post, Comment
3
-
+
3
4
4
admin.site.register(Post)
5
5
admin.site.register(Comment)
6
6
+
7

models.py

10 additions and 9 deletions.

View changes Hide changes
1
1
from django.utils import translation
2
2
from django.template.defaultfilters import slugify
3
3
from django.db import models
4
4
import datetime
5
5
import os
6
6
7
7
def post_title_directory(instance, filename):
8
8
    """ Files will be uploaded to MEDIA_ROOT/blog/<year of publishing>/<blog
9
9
    title>
10
10
    The blog title is determined by the text before the first period (".") in
11
11
    the filename. So if the file has the name "Trains are bæ.en.md", the file
12
12
    will be stored in "blog/<this year>/Trains are bæ". Name your files
13
13
    properly!
14
14
    It should also be noted that all files are stored in the same folder if they
15
15
    belong to the same blogpost, regardless of language. The titles that are
16
16
    displayed to the user however, should be the titles of the files themselves,
17
17
    which should be in the native language. So if a blog post is titled
18
18
    "Universities of Belgium", its Dutch counterpart should be titled
19
19
    "Universiteiten van België", so the correct title can be derived from the
20
20
    filename.
21
21
22
22
    Recommended way to name the uploaded file: "<name of blog post in language
23
23
    it's written>.org". This removes the maximum amount of redundancy (e.g. the
24
24
    language of the file can be derived from the title, no ".fr.org" or something
25
25
    like that necessary), and can directly be used for the end user (the title
26
26
    is what should be displayed).
27
27
    """
28
28
    english_file_name = os.path.basename(instance.english_file.name) # TODO: Test if this returns the file name!
29
29
    english_title = english_file_name.rpartition(".")[0]
30
30
    year = datetime.date.today().year
31
31
32
32
    return "blog/{0}/{1}/{2}".format(year, english_title, filename)
33
33
34
34
class Post(models.Model):
35
35
    """ Represents a blog post. The title of the blog post is determnined by the name
36
36
    of the files.
37
37
    A blog post can be in 5 different languages: German, Spanish, English, French,
38
38
    and Dutch. For all these languages, a seperate field exists. Thus, a
39
39
    translated blog post has a seperate file for each translation, and is
40
40
    seperated from Django's internationalization/localization system.
41
41
    Only the English field is mandatory. The others may contain a value if a
42
42
    translated version exists, which will be displayed accordingly.
43
43
    """
44
44
    published = models.DateTimeField(auto_now_add=True)
45
45
    english_file = models.FileField(upload_to=post_title_directory, unique=True, blank=False)
46
46
    dutch_file = models.FileField(upload_to=post_title_directory, blank=True)
47
47
    french_file = models.FileField(upload_to=post_title_directory, blank=True)
48
48
    german_file = models.FileField(upload_to=post_title_directory, blank=True)
49
49
    spanish_file = models.FileField(upload_to=post_title_directory, blank=True)
50
50
    # Only the English file can be unique, because apparantly, there can't be
51
51
    # two blank fields in a unique column. Okay then.
52
52
53
53
    def __str__(self):
54
54
        return self.slug("en")
55
55
56
56
    def slug(self, language_code=translation.get_language()):
57
57
        """ Returns a slug of the requested language, or None if no version exists in that language. """
58
58
        possibilities = {
59
59
            "en" : self.english_file,
60
60
            "de" : self.german_file,
61
61
            "nl" : self.dutch_file,
62
62
            "fr" : self.french_file,
63
63
            "es" : self.spanish_file,
64
64
            }
65
65
        if possibilities[language_code]:
66
66
            return slugify(os.path.basename(possibilities[language_code].name).rpartition(".")[0])
67
67
        else:
68
68
            return None
69
69
70
70
class Comment(models.Model):
71
71
    """ Represents a comment on a blog post.
72
72
73
-
    Comments are not linked to an account or anything, I'm trusting the
74
-
    commenter that he is honest with his credentials. That being said:
75
-
    XXX: Remember to put up a notification that comments are not checked for
76
-
    identity, and, unless verified by a trustworthy source, cannot be seen as
77
-
    being an actual statement from the commenter.
78
-
    Comments are linked to a blogpost, and are not filtered by language. (So a
79
-
    comment made by someone reading the article in Dutch, that's written in
+
73
    comment made by someone reading the article in Dutch, that's written in
80
74
    Dutch, will show up (unedited) for somebody whom's reading the Spanish
81
75
    version.
82
76
    XXX: Remember to notify (tiny footnote or something) that comments showing
83
-
    up in a foreign language is by design, and not a bug.
84
-
    """
85
77
    date = models.DateTimeField(auto_now_add=True)
86
78
    name = models.CharField(max_length=64)
87
79
    text = models.TextField(max_length=1000)  # Should be more than enough.
88
80
    post = models.ForeignKey(
89
81
        Post,
90
82
        on_delete=models.CASCADE,
91
83
        null=False,
92
84
        )
93
85
    class meta:
94
86
        ordering = ['date']  # When printed, prints the oldest comment first.
95
87
+
88
class FeedItem(models.Model):
+
89
    """ An item that shows up in the RSS feed."""
+
90
    title = models.CharField(max_length=64)
+
91
    added = models.DateTimeField(auto_now_add=True)
+
92
    description = models.CharField(max_length=400)
+
93
    link = models.CharField(max_length=64)
+
94
    soft_url = models.BooleanField(default=True)
+
95
+
96

templates/blog/feed.rss

20 additions and 0 deletions.

View changes Hide changes
+
1
<rss version="2.0">
+
2
    <channel>
+
3
    <title>RSS feed for maartenv.be</title>
+
4
    <link>https://maartenv.be</link>
+
5
    <description>The official RSS feed for the personal website of Maarten V.</description>
+
6
    {% for item in items %}
+
7
    <item>
+
8
        <title>{{ item.title }}</title>
+
9
        {% if item.soft_url %}
+
10
            <link>{% url item.link %}</title>
+
11
        {% else %}
+
12
            <link>{{ item.link }}</title>
+
13
        {% endif %}
+
14
        <description>{{ item.description }}</description>
+
15
        <pubDate>{{ item.added|time:'r' }}</pubDate>
+
16
    </item>
+
17
    {% endfor %}
+
18
    </channel>
+
19
</rss>
+
20

templates/blog/index.html

7 additions and 0 deletions.

View changes Hide changes
1
1
{% load i18n %}
2
2
{% load static %}
3
3
4
4
{% block stylesheets %}
5
5
{{ block.super }}
6
6
{#<link href="{% static "blog/stylesheet.css" %}" rel="stylesheet" media="screen, projection" />#}
7
7
{% endblock %}
8
8
9
9
{% block title %}{% trans "Maarten's blog" %}{% endblock title %}
10
10
11
11
{% block description %}{% blocktrans %}The always coherently put together, yet
12
12
fuzzy blog of whatever sprouts in my mind.{% endblocktrans %}
13
13
{% endblock description %}
14
14
15
15
{% block main %}
16
16
{% with color="brown" accent_color="yellow" %}
17
17
<div class="section {{ color }} z-depth-3">
18
18
    <div class="container">
19
19
        <div class="white-text">
20
20
            <h3>{% trans "Blog" %}</h3>
21
21
            <p>
22
22
                {% blocktrans %}Welcome to my blog. Here, I write
23
23
                about things that interest me. Politics, coding,
24
24
                studying, life, or anything else I fancy rambling
25
25
                about. If you're in luck, I may've written it in a
26
26
                language that you understand better than English.
27
27
                {% endblocktrans %}
28
28
            </p>
29
29
        </div>
30
30
    </div>
31
31
</div>
32
32
33
33
<div class="container row">
+
34
    <a href="{% url 'blog-feed' %}" class="btn-floating btn-large orange accent-4">
+
35
    <i class="large material-icons">rss_feed</i>
+
36
    </a>
+
37
</div>
+
38
+
39
+
40
<div class="container row">
34
41
    <div class="col s12 m6">
35
42
        <h1 id="weekly" class="{{ color }}-text">Weekly</h1>
36
43
        {% include "blog/weekly.html" %}
37
44
    </div>
38
45
    <div class="container col s12 m6">
39
46
        {% for title, date, blog_text, link in posts %}
40
47
            <h1 class="{{ color}}-text">{{ title }}</h2>
41
48
            <span class="grey-text">{{ date|date:"DATE_FORMAT" }}</span>
42
49
            <p class="hide-on-small-only">{{ blog_text|safe|truncatewords_html:100 }}</p>
43
50
            <a class="btn black-text waves-effect {{accent_color}} accent-4" href="{{link}}">
44
51
                📚 {% trans "Read on"%}
45
52
            </a>
46
53
            <hr />
47
54
        {% endfor %}
48
55
    </div>
49
56
</div>
50
57
{% endwith %}
51
58
{% endblock main %}
52
59

urls.py

1 addition and 0 deletions.

View changes Hide changes
1
1
2
2
from . import views # Imports the views from the same directory (which is views.py).
3
3
4
4
urlpatterns = [
5
5
    url(r'^$', views.index, name='blog-index'),
6
6
    url(r'^(?P<language>(.){2})/(?P<post_slug>(.)+)$', views.post, name='blog-post-language'),
7
7
    url(r'^(?P<post_slug>(.)+)$', views.post, name='blog-post'),
8
8
        ]
+
9
        ]
9
10

views.py

9 additions and 1 deletion.

View changes Hide changes
1
1
import subprocess
2
2
3
3
from django.utils.translation import ugettext as _
4
4
from django.shortcuts import get_object_or_404, render # This allows to render the template with the view here. It's pretty cool and important.
5
5
from django.http import HttpResponseRedirect, HttpResponse
6
6
from django.core.urlresolvers import reverse
7
7
from django.template import loader # This allows to actually load the template.
8
8
from .models import Post, Comment
9
-
from .forms import CommentForm
+
9
from .forms import CommentForm
10
10
from django.core.exceptions import ObjectDoesNotExist
11
11
from django.utils import translation
12
12
13
13
GERMAN = "de"
14
14
SPANISH = "es"
15
15
FRENCH = "fr"
16
16
DUTCH = "nl"
17
17
ENGLISH = "en"
18
18
19
19
footer_links = [
20
20
        [_("Blog main page"), "/blog"],
21
21
        [_("Contact"), "mailto:maarten.vangeneugden@student.uhasselt.be"],
22
22
        ]
23
23
footer_description = _("Maarten's personal blog, with sprinkles and a dollop of healthy bugs.")
24
24
25
25
# FIXME: Remove this template trash. THIS IS A VIEW, NOT A FUCKING TEMPLATE FFS
26
26
context = {
27
27
    'materialDesign_color': "green",
28
28
    'materialDesign_accentColor': "purple",
29
29
    'navbar_title': "Blog",
30
30
    'navbar_fixed': True,
31
31
    'navbar_backArrow': True,
32
32
    #'footer_title': "Maarten's blog",
33
33
    #'footer_description': "My personal scribbly notepad.",
34
34
    #'footer_links': footer_links,
35
35
    }
36
36
37
37
def org_to_html(file_path):
38
38
    """ Converts the given org formatted file to HTML.
39
39
    This function directly returns the resulting HTML code. This function uses
40
40
    the amazing Haskell library Pandoc to convert the file (and takes care
41
41
    of header id's and all that stuff).
42
42
    """
43
43
    # FIXME: Remove hardcoded link to media. Replace with media tag!
44
44
    return subprocess.check_output(["pandoc", "--from=org", "--to=html", "/srv/django/website/media/"+file_path])
45
45
46
46
def get_available_post_languages(post):
47
47
    """ Returns the language codes for which a blog post exists. This function
48
48
    always returns English (because that field mustn't be empty).
49
49
    So say a blog post has an English, Dutch and French version (which means
50
50
    english_file, french_file and dutch_file aren't empty), the function will return {"en",
51
51
    "fr", "nl"}. """
52
52
    available_languages = {ENGLISH}
53
53
    if post.german_file != "":
54
54
        available_languages.add(GERMAN)
55
55
    if post.spanish_file != "":
56
56
        available_languages.add(SPANISH)
57
57
    if post.french_file != "":
58
58
        available_languages.add(FRENCH)
59
59
    if post.dutch_file != "":
60
60
        available_languages.add(DUTCH)
61
61
    return available_languages
62
62
63
63
def get_preferred_post_language(post, language):
64
64
    """ Returns the post language file that best suits the given language. This
65
65
    is handy if you know what language the user prefers, but aren't sure whether
66
66
    you can provide that language. This function will try to provide the file
67
67
    for that language, or return English if that's not possible. """
68
68
    if language == GERMAN and post.german_file is not None:
69
69
        return post.german_file
70
70
    if language == SPANISH and post.spanish_file is not None:
71
71
        return post.spanish_file
72
72
    if language == FRENCH and post.french_file is not None:
73
73
        return post.french_file
74
74
    if language == DUTCH and post.dutch_file is not None:
75
75
        return post.dutch_file
76
76
    return post.english_file  # Returned if all other choices wouldn't be satisfactory, or the requested language is English.
77
77
78
78
def index(request):
79
79
    template = "blog/index.html"
80
80
    posts = Post.objects.all()
81
81
    language = translation.get_language()
82
82
83
83
    post_links = []
84
84
    for post in posts:
85
85
        blog_file = get_preferred_post_language(post, language)
86
86
        # TODO: Find a cleaner way to determine the title. First and foremost:
87
87
        # If the language differs from English, the other language file needs to
88
88
        # be loaded. Plus: look for a built in function to remove the full path
89
89
        # and only return the file name.
90
90
        title = (blog_file.name.rpartition("/")[2]).rpartition(".")[0]
91
91
        human_title = title.replace("_"," ")
92
92
        date = post.published
93
93
        blog_text = org_to_html(blog_file.name)
94
94
        # TODO: The link can possibly be reversed in the DTL using the title, which is actually
95
95
        # a cleaner way to do it. Investigate.
96
96
        link = reverse("blog-post-language", args=[language, post.slug(language)])
97
97
        post_links.append([human_title, date, blog_text, link])
98
98
99
99
    context = {
100
100
            'posts': post_links,
101
101
            'materialDesign_color': "brown",
102
102
            'materialDesign_accentColor': "yellow",
103
103
            'navbar_title': _("Notepad from a student"),
104
104
            'navbar_backArrow': True,
105
105
            'footer_links': footer_links,
106
106
            'footer_description': footer_description,
107
107
            }
108
108
    return render(request, template, context)
109
109
110
110
def post(request, post_slug, language=None):
111
111
    if request.method == "POST":  # Handling a reply if one is sent
112
112
        form = CommentForm(request.POST)
113
113
        if form.is_valid():
114
114
            form.save()
115
115
116
116
    if language is not None:
117
117
        if translation.check_for_language(language):
118
118
            translation.activate(language)
119
119
            request.session[translation.LANGUAGE_SESSION_KEY] = language
120
120
            #return post(request, post_slug)
121
121
    else:
122
122
        language = translation.get_language()
123
123
124
124
    template = "blog/post.html"
125
125
    posts = Post.objects.all()
126
126
    #comments = Comment.objects.filter(post
127
127
    for post in posts:
128
128
        if post.slug(language) == post_slug:
129
129
            comments = Comment.objects.filter(post=post)
130
130
            form = CommentForm(initial={'post': post})
131
131
            blog_file = get_preferred_post_language(post, language)
132
132
            blog_text = org_to_html(blog_file.name)
133
133
            context = {
134
134
                'comments': comments,
135
135
                'form' : form,
136
136
                'human_post_title': blog_file.name.replace("_"," "),
137
137
                'materialDesign_color': "brown",
138
138
                'materialDesign_accentColor': "yellow",
139
139
                'article': blog_text,
140
140
                'title': str(blog_file),
141
141
                'navbar_title': ((blog_file.name.rpartition("/")[2]).rpartition(".")[0]).replace("_"," "),
142
142
                'navbar_backArrow': False,
143
143
                'post_slug': post_slug,
144
144
                'footer_links': footer_links,
145
145
                'footer_description': footer_description,
146
146
                }
147
147
148
148
            # Getting all available article links
149
149
            available = get_available_post_languages(post)
150
150
            if ENGLISH in available:
151
151
                context['english_link'] = reverse("blog-post-language", args=[ENGLISH, post.slug(ENGLISH)])
152
152
            if DUTCH in available:
153
153
                context['dutch_link'] = reverse("blog-post-language", args=[DUTCH, post.slug(DUTCH)])
154
154
155
155
            if FRENCH in available:
156
156
                context['french_link'] = reverse("blog-post-language", args=[FRENCH, post.slug(FRENCH)])
157
157
158
158
            if SPANISH in available:
159
159
                context['spanish_link'] = reverse("blog-post-language", args=[SPANISH, post.slug(SPANISH)])
160
160
161
161
            if GERMAN in available:
162
162
                context['german_link'] = reverse("blog-post-language", args=[GERMAN, post.slug(GERMAN)])
163
163
164
164
            return render(request, template, context)
165
165
+
166
def rss(request):
+
167
    template = "blog/feed.rss"
+
168
    context = {
+
169
        'items': ItemFeed.objects.all(),
+
170
        }
+
171
    return render(request, template, context)
+
172
+
173