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, 11: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 |