blog

Improve handling of different localizations

Slug names and titles used to get derived from the name of the file itself, but Django renames these files for convenience, thus losing all the information. I've changed that now, and slugs and titles are now hardcoded in the database, so they're always right. Some methods have been removed, some were added (pretty redundant ones but oh well Python), but I'll make the migrations soon and see if it still works properly.

Author
Maarten 'Vngngdn' Vangeneugden
Date
April 9, 2018, 9:24 p.m.
Hash
fbff300ceba7e4f07548d25bdec66c24b29e2fae
Parent
92aabadd409a496b50a23d3920ffa653025a3903
Modified files
models.py
views.py

models.py

79 additions and 15 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
-
    dutch_file = models.FileField(upload_to=post_title_directory, blank=True)
47
-
    french_file = models.FileField(upload_to=post_title_directory, blank=True)
+
46
    dutch_file = models.FileField(upload_to=post_title_directory, blank=False)
+
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
-
    # two blank fields in a unique column. Okay then.
52
-
+
51
    title_en = models.CharField(max_length=64, unique=True, blank=False)
+
52
    title_nl = models.CharField(max_length=64, unique=True, blank=False)
+
53
    title_fr = models.CharField(max_length=64, blank=True)
+
54
    title_de = models.CharField(max_length=64, blank=True)
+
55
    title_es = models.CharField(max_length=64, blank=True)
+
56
    # PostgreSQL sees two null values as not unique, therefore I can't ask for unique names.
+
57
+
58
    slug_en  = models.SlugField(unique=True, blank=False, allow_unicode=True)
+
59
    slug_nl  = models.SlugField(unique=True, blank=False, allow_unicode=True)
+
60
    slug_fr  = models.SlugField(blank=True, allow_unicode=True)
+
61
    slug_de  = models.SlugField(blank=True, allow_unicode=True)
+
62
    slug_es  = models.SlugField(blank=True, allow_unicode=True)
+
63
53
64
    def __str__(self):
54
65
        return self.slug("en")
55
66
56
67
    def slug(self, language_code=translation.get_language()):
+
68
        if language_code == "en":
+
69
            return self.english_file
+
70
        elif language_code == "nl":
+
71
            return self.dutch_file
+
72
        elif language_code == "de":
+
73
            if self.german_file is None:
+
74
                return self.english_file
+
75
            else:
+
76
                return self.german_file
+
77
        elif language_code == "fr":
+
78
            if self.french_file is None:
+
79
                return self.english_file
+
80
            else:
+
81
                return self.french_file
+
82
        elif language_code == "es":
+
83
            if self.spanish_file is None:
+
84
                return self.english_file
+
85
            else:
+
86
                return self.spanish_file
+
87
        return self.english_file
+
88
+
89
    def title(self, language_code=translation.get_language()):
+
90
        if language_code == "en":
+
91
            return self.title_en
+
92
        elif language_code == "nl":
+
93
            return self.title_nl
+
94
        elif language_code == "de":
+
95
            if self.title_de == "":
+
96
                return self.title_en
+
97
            else:
+
98
                return self.title_de
+
99
        elif language_code == "fr":
+
100
            if self.title_fr == "":
+
101
                return self.title_en
+
102
            else:
+
103
                return self.title_fr
+
104
        elif language_code == "es":
+
105
            if self.title_es == "":
+
106
                return self.title_en
+
107
            else:
+
108
                return self.title_es
+
109
        return self.title_en
+
110
+
111
    def slug(self, language_code=translation.get_language()):
57
112
        """ Returns a slug of the requested language, or None if no version exists in that language. """
58
113
        possibilities = {
59
-
            "en" : self.english_file,
60
-
            "de" : self.german_file,
61
-
            "nl" : self.dutch_file,
62
-
            "fr" : self.french_file,
63
-
            "es" : self.spanish_file,
64
-
            }
65
-
        if possibilities[language_code]:
66
-
            return slugify(os.path.basename(possibilities[language_code].name).rpartition(".")[0])
67
-
        else:
68
-
            return None
69
-
+
114
            return self.slug_en
+
115
        elif language_code == "nl":
+
116
            return self.slug_nl
+
117
        elif language_code == "de":
+
118
            if self.slug_de == "":
+
119
                return self.slug_en
+
120
            else:
+
121
                return self.slug_de
+
122
        elif language_code == "fr":
+
123
            if self.slug_fr == "":
+
124
                return self.slug_en
+
125
            else:
+
126
                return self.slug_fr
+
127
        elif language_code == "es":
+
128
            if self.slug_es == "":
+
129
                return self.slug_en
+
130
            else:
+
131
                return self.slug_es
+
132
        return self.slug_en
+
133
70
134
class Comment(models.Model):
71
135
    """ Represents a comment on a blog post.
72
136
    Comments are not filtered by language; a
73
137
    comment made by someone reading the article in Dutch, that's written in
74
138
    Dutch, will show up (unedited) for somebody whom's reading the Spanish
75
139
    version.
76
140
    """
77
141
    date = models.DateTimeField(auto_now_add=True)
78
142
    name = models.CharField(max_length=64)
79
143
    text = models.TextField(max_length=1000)  # Should be more than enough.
80
144
    post = models.ForeignKey(
81
145
        Post,
82
146
        on_delete=models.CASCADE,
83
147
        null=False,
84
148
        )
85
149
    class meta:
86
150
        ordering = ['date']  # When printed, prints the oldest comment first.
87
151
88
152
class FeedItem(models.Model):
89
153
    """ An item that shows up in the RSS feed."""
90
154
    title = models.CharField(max_length=64)
91
155
    added = models.DateTimeField(auto_now_add=True)
92
156
    description = models.CharField(max_length=400)
93
157
    link = models.URLField()
94
158

views.py

10 additions and 48 deletions.

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