Big overhaul for 2022
- Author
- Maarten Vangeneugden
- Date
- Feb. 20, 2022, 5:55 p.m.
- Hash
- 68708a08cb93b5efe5d9d30733855940c45a0c46
- Parent
- d3256e09ca8dd1f736407a83edf49f2ca60d16d6
- Modified files
- models.py
- templates/blog/index.djhtml
- templates/blog/post.djhtml
- urls.py
- views.py
models.py ¶
125 additions and 116 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 |
import datetime |
5 |
6 |
import os |
6 |
7 |
|
7 |
8 |
def post_title_directory(instance, filename): |
8 |
- | """ Files will be uploaded to MEDIA_ROOT/blog/<year of publishing>/<blog |
9 |
- | title> |
10 |
- | The blog title is determined by the text before the first period (".") in |
11 |
- | the filename. So if the file has the name "Trains are bæ.en.md", the file |
12 |
- | will be stored in "blog/<this year>/Trains are bæ". Name your files |
13 |
- | properly! |
14 |
- | It should also be noted that all files are stored in the same folder if they |
15 |
- | belong to the same blogpost, regardless of language. The titles that are |
16 |
- | displayed to the user however, should be the titles of the files themselves, |
17 |
- | which should be in the native language. So if a blog post is titled |
18 |
- | "Universities of Belgium", its Dutch counterpart should be titled |
19 |
- | "Universiteiten van België", so the correct title can be derived from the |
20 |
- | filename. |
21 |
- | |
22 |
- | Recommended way to name the uploaded file: "<name of blog post in language |
23 |
- | it's written>.org". This removes the maximum amount of redundancy (e.g. the |
24 |
- | language of the file can be derived from the title, no ".fr.org" or something |
25 |
- | like that necessary), and can directly be used for the end user (the title |
26 |
- | is what should be displayed). |
27 |
- | """ |
+ |
9 |
- For each post, there's no longer a mandatory Dutch and English |
+ |
10 |
version. Instead, only the title needs to be in multiple languages. |
+ |
11 |
- There's a new table for the links to the articles themselves. These include a |
+ |
12 |
language code and a foreign key to the post they belong to. |
+ |
13 |
- If an article is available in the active language, but not tagged for the same |
+ |
14 |
dialect, then it should just show up without any warnings. |
+ |
15 |
- If an article is not available in the active language, only the title should |
+ |
16 |
show up, but where the short intro text would normally be, there should be an |
+ |
17 |
explanation that it's only available in other languages, and provide links to |
+ |
18 |
those versions. |
+ |
19 |
""" |
+ |
20 |
|
+ |
21 |
def org_to_html(file_path): |
+ |
22 |
""" Converts the given org formatted file to HTML. |
+ |
23 |
This function directly returns the resulting HTML code. This function uses |
+ |
24 |
the amazing Haskell library Pandoc to convert the file (and takes care |
+ |
25 |
of header id's and all that stuff). |
+ |
26 |
""" |
28 |
27 |
english_file_name = os.path.basename(instance.english_file.name) # TODO: Test if this returns the file name! |
29 |
- | english_title = english_file_name.rpartition(".")[0] |
30 |
- | year = datetime.date.today().year |
31 |
- | |
+ |
28 |
# XXX: The reason I'm first converting all occurences of .jpg][ and .png][ |
+ |
29 |
# to .jpgPANDOCBUG][ and .pngPANDOCBUG][, is because of a Pandoc bug that |
+ |
30 |
# removes the text links for images. It is afterwards converted back, no |
+ |
31 |
# worries. |
+ |
32 |
file = open("/srv/django/website/media/"+file_path, "r", encoding="utf-8") |
+ |
33 |
text = file.read() |
+ |
34 |
file.close() |
+ |
35 |
text = text.replace(".jpg][", ".jpgPANDOCBUG][") |
+ |
36 |
text = text.replace(".png][", ".pngPANDOCBUG][") |
+ |
37 |
file = open("/tmp/blog-file.org", "w", encoding="utf-8") |
+ |
38 |
file.write(text) |
+ |
39 |
file.close() |
+ |
40 |
html_text = subprocess.check_output(["pandoc", "--from=org", "--to=html","/tmp/blog-file.org"]) |
+ |
41 |
html_text = html_text.decode("utf-8").replace(".jpgPANDOCBUG", ".jpg") |
+ |
42 |
html_text = html_text.replace(".pngPANDOCBUG", ".png") |
+ |
43 |
return html_text |
+ |
44 |
|
+ |
45 |
class Article(models.Model): |
+ |
46 |
AFRIKAANS = 'af' |
+ |
47 |
BELGIAN_FRENCH = 'fr-be' |
+ |
48 |
DUTCH = 'nl' |
+ |
49 |
ESPERANTO = 'eo' |
+ |
50 |
FLEMISH = 'nl-be' |
+ |
51 |
FRENCH = 'fr' |
+ |
52 |
GERMAN = 'de' |
+ |
53 |
SPANISH = 'es' |
+ |
54 |
|
+ |
55 |
LANGUAGE_CODES = [ |
+ |
56 |
(AFRIKAANS, 'Afrikaans'), |
+ |
57 |
(BELGIAN_FRENCH, 'Français (Belgique)'), |
+ |
58 |
(DUTCH, 'Nederlands'), |
+ |
59 |
(ESPERANTO, 'Esperanto'), |
+ |
60 |
(FLEMISH, 'Vlaams'), |
+ |
61 |
(FRENCH, 'Français'), |
+ |
62 |
(GERMAN, 'Deutsch'), |
+ |
63 |
(SPANISH, 'Español')] |
+ |
64 |
|
+ |
65 |
# auto_now_add should normally be True, but by changing a lot of the |
+ |
66 |
# internals I had to temporary disable it |
+ |
67 |
visible = models.BooleanField(default=True) |
+ |
68 |
post = models.ForeignKey(Post, on_delete=models.CASCADE) |
+ |
69 |
language_code = models.CharField(max_length=16, |
+ |
70 |
choices = LANGUAGE_CODES, |
+ |
71 |
blank=False) |
+ |
72 |
# file_path shouldn't be unique, because the same article file could be used |
+ |
73 |
# for multiple dialects of the same language. |
+ |
74 |
file_path = models.FilePathField(path=settings.MEDIA_ROOT + "blog/articles/", |
+ |
75 |
blank=False) |
+ |
76 |
# Same reason, slug shouldn't be unique |
+ |
77 |
slug = models.SlugField(unique=False, blank=False, allow_unicode=True) |
+ |
78 |
|
+ |
79 |
def text(self): |
+ |
80 |
return org_to_html(self.file_path) |
+ |
81 |
|
+ |
82 |
def title(self): |
+ |
83 |
return self.post.title(self.language_code) |
+ |
84 |
|
+ |
85 |
|
32 |
86 |
return "blog/{0}/{1}/{2}".format(year, english_title, filename) |
33 |
- | |
34 |
87 |
class Post(models.Model): |
35 |
88 |
""" Represents a blog post. The title of the blog post is determnined by the name |
36 |
- | of the files. |
37 |
- | A blog post can be in 5 different languages: German, Spanish, English, French, |
38 |
- | and Dutch. For all these languages, a seperate field exists. Thus, a |
39 |
- | translated blog post has a seperate file for each translation, and is |
40 |
- | seperated from Django's internationalization/localization system. |
41 |
- | Only the English field is mandatory. The others may contain a value if a |
42 |
- | translated version exists, which will be displayed accordingly. |
43 |
- | """ |
44 |
- | published = models.DateTimeField(auto_now_add=True) |
45 |
- | english_file = models.FileField(upload_to=post_title_directory, blank=False) |
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 |
- | german_file = models.FileField(upload_to=post_title_directory, blank=True) |
49 |
- | spanish_file = models.FileField(upload_to=post_title_directory, blank=True) |
50 |
- | |
+ |
89 |
published = models.DateTimeField(auto_now_add=False) |
+ |
90 |
|
51 |
91 |
title_en = models.CharField(max_length=64, unique=True, blank=False) |
52 |
92 |
title_nl = models.CharField(max_length=64, unique=True, blank=False) |
53 |
93 |
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 |
- | |
+ |
94 |
title_de = models.CharField(max_length=64, unique=True, blank=True) |
+ |
95 |
title_es = models.CharField(max_length=64, unique=True, blank=True) |
+ |
96 |
title_eo = models.CharField(max_length=64, unique=True, blank=True) |
+ |
97 |
title_af = models.CharField(max_length=64, unique=True, blank=True) |
+ |
98 |
|
58 |
99 |
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 |
- | |
64 |
100 |
def __str__(self): |
65 |
101 |
return self.slug("en") |
66 |
- | |
67 |
- | def text_file(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 == "": |
74 |
- | return self.english_file |
75 |
- | else: |
76 |
- | return self.german_file |
77 |
- | elif language_code == "fr": |
78 |
- | if self.french_file == "": |
79 |
- | return self.english_file |
80 |
- | else: |
81 |
- | return self.french_file |
82 |
- | elif language_code == "es": |
83 |
- | if self.spanish_file == "": |
84 |
- | return self.english_file |
85 |
- | else: |
86 |
- | return self.spanish_file |
87 |
- | return self.english_file |
88 |
- | |
+ |
102 |
|
+ |
103 |
|
+ |
104 |
def articles(self): |
+ |
105 |
return Article.objects.filter(post=self) |
+ |
106 |
|
+ |
107 |
def article(self, language_code=translation.get_language()): |
+ |
108 |
# Retrieves all articles that have this post as their foreign key |
+ |
109 |
articles = Article.objects.filter(post=self) |
+ |
110 |
for a in articles: |
+ |
111 |
if a.language_code == language_code: |
+ |
112 |
return a |
+ |
113 |
# If no exact match was found, try again, but now accept other dialects |
+ |
114 |
# as well: |
+ |
115 |
for a in articles: |
+ |
116 |
if language_code.startswith(a.language_code): |
+ |
117 |
return a |
+ |
118 |
|
+ |
119 |
# If still no article was found, return None |
+ |
120 |
return None |
+ |
121 |
|
89 |
122 |
def title(self, language_code=translation.get_language()): |
90 |
123 |
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 |
+ |
124 |
'de': self.title_de, |
+ |
125 |
'es': self.title_es, |
+ |
126 |
'en': self.title_en, |
+ |
127 |
'eo': self.title_eo, |
+ |
128 |
'fr': self.title_fr, |
+ |
129 |
'nl': self.title_nl} |
+ |
130 |
for code, translated_title in options.items(): |
+ |
131 |
if language_code.startswith(code): |
+ |
132 |
return translated_title |
+ |
133 |
# If no return has happened, default to English |
+ |
134 |
return self.title_en |
110 |
135 |
|
111 |
- | def slug(self, language_code=translation.get_language()): |
112 |
- | """ Returns a slug of the requested language, or None if no version exists in that language. """ |
113 |
- | if language_code == "en": |
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 |
- | |
+ |
136 |
|
134 |
137 |
class Comment(models.Model): |
135 |
138 |
""" Represents a comment on a blog post. |
136 |
139 |
Comments are not filtered by language; a |
137 |
140 |
comment made by someone reading the article in Dutch, that's written in |
138 |
141 |
Dutch, will show up (unedited) for somebody whom's reading the Spanish |
139 |
142 |
version. |
140 |
143 |
""" |
141 |
144 |
date = models.DateTimeField(auto_now_add=True) |
142 |
- | name = models.CharField(max_length=64) |
143 |
- | text = models.TextField(max_length=1000) # Should be more than enough. |
144 |
- | post = models.ForeignKey( |
+ |
145 |
visible = models.BooleanField(default=True) |
+ |
146 |
# auto_now_add should normally be True, but by changing a lot of the |
+ |
147 |
# internals I had to temporary disable it |
+ |
148 |
date = models.DateTimeField(auto_now_add=False) |
+ |
149 |
name = models.CharField(max_length=64, blank=True) |
+ |
150 |
text = models.TextField(max_length=10000, blank=False) # Should be more than enough |
+ |
151 |
# reaction_to is null if it's not a reaction to an existing comment |
+ |
152 |
reaction_to = models.ForeignKey(Comment, on_delete=models.CASCADE, null=True) |
+ |
153 |
post = models.ForeignKey( |
145 |
154 |
Post, |
146 |
155 |
on_delete=models.CASCADE, |
147 |
156 |
null=False, |
148 |
157 |
) |
149 |
158 |
class meta: |
150 |
159 |
ordering = ['date'] # When printed, prints the oldest comment first. |
151 |
160 |
|
152 |
161 |
class FeedItem(models.Model): |
153 |
162 |
""" An item that shows up in the RSS feed.""" |
154 |
163 |
title = models.CharField(max_length=64) |
155 |
164 |
added = models.DateTimeField(auto_now_add=True) |
156 |
165 |
description = models.CharField(max_length=400) |
157 |
166 |
link = models.URLField() |
158 |
167 |
templates/blog/index.djhtml ¶
36 additions and 3 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 |
<style> |
7 |
7 |
img { |
8 |
8 |
width: 80%; |
9 |
9 |
display: block; |
10 |
10 |
margin-left: auto; |
11 |
11 |
margin-right: auto; |
12 |
12 |
} |
13 |
13 |
video { |
14 |
14 |
width: 80%; |
15 |
15 |
} |
16 |
16 |
</style> |
17 |
17 |
{% endblock %} |
18 |
18 |
|
19 |
19 |
{% block title %}{% trans "Maarten's blog" %}{% endblock title %} |
20 |
20 |
|
21 |
21 |
{% block description %}{% blocktrans %}The always coherently put together, yet |
22 |
22 |
fuzzy blog of whatever sprouts in my mind.{% endblocktrans %} |
23 |
23 |
{% endblock description %} |
24 |
24 |
|
25 |
25 |
{% block header %} |
26 |
26 |
<header> |
27 |
27 |
<h1>{% trans "Notepad of a student" %}</h1> |
28 |
28 |
<label for="nav-drawer-toggle">≡</label> |
29 |
29 |
</header> |
30 |
30 |
{% endblock header %} |
31 |
31 |
|
32 |
32 |
{% block nav %} |
33 |
33 |
<input id="nav-drawer-toggle" type="checkbox" /> |
34 |
34 |
<nav> |
35 |
35 |
<label for="nav-drawer-toggle"><!--🡨-->🡠</label> |
36 |
36 |
<h2>{% trans "Navigation" %}</h2> |
37 |
37 |
{% for title, date, blog_text, link in posts %} |
38 |
38 |
<a class="nav-link" href="{{ link }}">{{ title }}</a> |
39 |
39 |
{% endfor %} |
40 |
40 |
<hr class="half" /> |
41 |
41 |
<a class="nav-link" href="{% url 'about-index' %}">{% trans "Front page" %}</a> |
42 |
42 |
</nav> |
43 |
43 |
{% endblock nav %} |
44 |
44 |
|
45 |
45 |
{% block main %} |
46 |
46 |
|
47 |
47 |
<section class="emphasis"> |
48 |
48 |
<h1>{% trans "Blog" %}</h1> |
49 |
49 |
<p> |
50 |
50 |
{% blocktrans %}Welcome to my blog. Here, I write |
51 |
51 |
about things that interest me. Politics, coding, |
52 |
52 |
studying, life, or anything else I fancy rambling |
53 |
53 |
about. If you're in luck, I may've written it in a |
54 |
54 |
language that you understand better than English. |
55 |
55 |
{% endblocktrans %} |
56 |
56 |
</p> |
57 |
57 |
</section> |
58 |
58 |
|
59 |
59 |
<div class="fab"> |
60 |
60 |
<a href="{% url 'blog-feed' %}" id="feed-fab"> |
61 |
61 |
<b>RSS</b> |
62 |
62 |
</a> |
63 |
63 |
</div> |
64 |
64 |
|
65 |
65 |
<section> |
66 |
66 |
<h1>Monthly</h1> |
67 |
67 |
<a class="btn fill" href="{% url "monthly-archive" %}"> {% trans "Open archive" %}</a> |
68 |
- | {% include "blog/monthly.html" %} |
+ |
68 |
{% include "blog/monthly.html" %} |
69 |
69 |
</section> |
70 |
70 |
|
71 |
71 |
<div class="cards"> |
72 |
72 |
{% for title, date, blog_text, link in posts %} |
+ |
73 |
<div class="card"> |
+ |
74 |
<h3>{{ post.title }}</h3> |
+ |
75 |
<h4>{{ post.published|date:"DATE_FORMAT" }}</h4> |
+ |
76 |
{% if post.article %} |
+ |
77 |
{# No need for surrounding <p> tags #} |
+ |
78 |
{{ post.article_text|safe|truncatewords_html:100 }} |
+ |
79 |
{# Pandoc automatically makes those #} |
+ |
80 |
<a class="btn outline" href="{% url 'blog-post' post.article.language_code post.article.slug %}"> |
+ |
81 |
📚 {% translate "Read on" %}</a> |
+ |
82 |
{% else %} |
+ |
83 |
{% get_current_language as CUR_LANG %} |
+ |
84 |
<p>{% blocktranslate cur_lang=CUR_LANG.name_translated %}This blog |
+ |
85 |
post is not available in {{ cur_lang }}.{% endblocktranslate %} |
+ |
86 |
{% blocktranslate count counter=post.articles|length %} |
+ |
87 |
However, it is available in |
+ |
88 |
{% plural %} |
+ |
89 |
If you want, you can choose to read it in one of these |
+ |
90 |
languages: <br> |
+ |
91 |
{% endblocktranslate %} |
+ |
92 |
{% for article in post.articles %} |
+ |
93 |
{% get_language_info for article.language_code as lang %} |
+ |
94 |
{{ lang.name_translated }}: |
+ |
95 |
<a href="{% url blog-post article.language_code article.slug %}"> |
+ |
96 |
{{ article.title }} |
+ |
97 |
</a> |
+ |
98 |
{% if not forloop.last %}<br>{% endif %} |
+ |
99 |
{% endfor %} |
+ |
100 |
</p> |
+ |
101 |
{% endif %} |
+ |
102 |
</div> |
+ |
103 |
{% endfor %} |
+ |
104 |
{% comment %} |
+ |
105 |
{% for title, date, blog_text, link in posts %} |
73 |
106 |
<div class="card"> |
74 |
107 |
<h3>{{ title }}</h3> |
75 |
108 |
<h4>{{ date|date:"DATE_FORMAT" }}</h4> |
76 |
109 |
<p>{{ blog_text|safe|truncatewords_html:100 }}</p> |
77 |
- | <a class="btn outline" href="{{link}}">📚 {% trans "Read on" %}</a> |
78 |
- | </div> |
+ |
110 |
</div> |
79 |
111 |
{% endfor %} |
80 |
112 |
</div> |
+ |
113 |
</div> |
81 |
114 |
|
82 |
115 |
{% endblock main %} |
83 |
116 |
templates/blog/post.djhtml ¶
4 additions and 17 deletions.
View changes Hide changes
1 |
1 |
{% load humanize %} |
2 |
2 |
{% load i18n %} |
3 |
3 |
{% load static %} |
4 |
4 |
|
5 |
5 |
{% block description %} |
6 |
6 |
{{ article|safe|truncatewords_html:10 }} |
7 |
- | {% endblock description %} |
+ |
7 |
{% endblock description %} |
8 |
8 |
{% block title %}📚 {{ navbar_title }}{% endblock title %} |
9 |
9 |
|
10 |
10 |
|
11 |
11 |
{% block header %} |
12 |
12 |
<header> |
13 |
13 |
<h1>{{ navbar_title }}</h1> |
14 |
14 |
</header> |
15 |
15 |
{% endblock header %} |
16 |
16 |
{% block main %} |
17 |
17 |
<section style="font-family: serif;"> |
18 |
18 |
<!--<article style="font-family:serif;">--> |
19 |
19 |
{{ article|safe }} |
20 |
- | <!--</article>--> |
+ |
20 |
<!--</article>--> |
21 |
21 |
</section> |
22 |
22 |
|
23 |
23 |
<h5 class="white-text">{% trans "This article in other languages" %}</h5> |
+ |
24 |
<h5 class="white-text">{% trans "This article in other languages" %}</h5> |
24 |
25 |
|
25 |
26 |
{% get_language_info for 'nl' as LANG %} |
26 |
27 |
<a {% if dutch_link %} href="{{dutch_link}}" {% endif %} |
27 |
28 |
class="btn fill |
28 |
29 |
{% if not dutch_link %}disabled{% endif %}"> |
29 |
30 |
🇧🇪 {{ LANG.name_translated}} 🇳🇱 |
30 |
31 |
</a> |
31 |
32 |
{% get_current_language as lang %} |
32 |
33 |
{% get_language_info for 'fr' as LANG %} |
33 |
34 |
<a {% if french_link %} href="{{french_link}}" {% endif %} |
34 |
35 |
class="btn fill |
35 |
36 |
{% if not french_link %}disabled{% endif %}"> |
36 |
37 |
🇧🇪 {{ LANG.name_translated}} 🇫🇷 |
37 |
38 |
</a> |
38 |
39 |
{% get_language_info for 'en' as LANG %} |
39 |
40 |
<a {% if english_link %} href="{{english_link}}" {% endif %} |
40 |
41 |
class="btn fill |
41 |
42 |
{% if not english_link %}disabled{% endif %}"> |
42 |
43 |
🇬🇧 {{ LANG.name_translated}} 🇺🇸 |
43 |
44 |
</a> |
44 |
45 |
{% comment %} |
45 |
- | {% get_language_info for 'de' as LANG %} |
46 |
46 |
<a {% if german_link %} href="{{german_link}}" {% endif %} |
47 |
47 |
class="btn fill |
48 |
48 |
{% if not german_link %}disabled{% endif %}"> |
49 |
49 |
🇧🇪 {{ LANG.name_translated}} 🇩🇪 |
50 |
50 |
</a> |
51 |
51 |
{% comment %} |
52 |
- | {% get_language_info for 'es' as LANG %} |
53 |
52 |
<a {% if spanish_link %} href="{{spanish_link}}" {% endif %} |
54 |
53 |
class="btn |
55 |
54 |
{% if not spanish_link %}disabled{% endif %}"> |
56 |
55 |
🇪🇸 {{ LANG.name_translated}} 🇲🇽 |
57 |
56 |
</a> |
58 |
57 |
|
+ |
58 |
|
59 |
59 |
<h5 class="white-text">{% trans "Comments" %}</h5> |
60 |
60 |
{% for comment in comments %} {# Whoops =P #} |
61 |
61 |
<span class="white-text">{{ comment.name|title }} | </span> |
62 |
62 |
<time class="grey-text" datetime="{{ comment.date|date:'c' }}">{{ comment.date|naturaltime }}</time> |
63 |
63 |
<br /> |
64 |
64 |
<p class="white-text">{{ comment.text|urlize }}</p> |
65 |
65 |
<hr /> |
66 |
66 |
{% endfor %} |
67 |
67 |
{# Form for new comment #} |
68 |
68 |
<form action="" method="POST"> |
69 |
69 |
{% csrf_token %} |
70 |
70 |
{{ form.name.label_tag }} |
71 |
71 |
<input class="white-text browser-default" type="text" id="id_name" name="name" maxlength="64" required /> |
72 |
72 |
{{ form.text.label_tag }} |
73 |
73 |
<textarea id="id_text" class="white-text" name="text" maxlength="1000" required></textarea> |
74 |
74 |
<input type="submit" value="{% trans "Submit" %}" /> |
75 |
75 |
</form> |
76 |
76 |
|
77 |
77 |
|
78 |
78 |
{% comment %} |
79 |
79 |
<a href="{% url 'blog-post' post_slug %}" class="btn {{accent_color}} accent-4 black-text tooltipped" data-position="bottom" data-delay="50" data-tooltip="{% trans "Multilingual link. Links to the version in the viewer's preferred language." %}">🏳️🌈 {% trans "All available languages" %}</a> |
80 |
80 |
{# TODO: Change to rainbow flag when possible #} |
81 |
81 |
{% endcomment %} |
82 |
82 |
</div> |
83 |
83 |
|
84 |
84 |
</div> |
85 |
85 |
<div class="container"> |
86 |
- | {% for title, date, description, link in post_links %} |
87 |
- | <h2 class="{{ color}}-text">{{ title }}</h2> |
88 |
- | {# FIXME: Date is in all languages of the same format. Fix for each language #} |
89 |
- | <span class="grey-text">{{ date|date:"l j F Y" }}</span> |
90 |
- | {#<p class="hide-on-small-only">{{ description }}</p>#} |
91 |
- | <p class="hide-on-small-only">{% lorem %}</p> |
92 |
- | <a class="btn {{accent_color}} accent-3" href="{{link}}"> |
93 |
- | {% trans "Read on"%} |
94 |
- | </a> |
95 |
- | <hr /> |
96 |
- | {% endfor %} |
97 |
- | </div> |
98 |
- | {% endblock main %} |
99 |
86 |
urls.py ¶
10 additions and 2 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 |
path('', views.index, name='blog-index'), |
+ |
6 |
path('feed.rss', views.rss, name='blog-feed'), |
+ |
7 |
path('<slug:language_code>/<slug:post_slug>', views.post, name='blog-post'), |
+ |
8 |
path('arkivo' views.archive, name='blog-archive'), |
+ |
9 |
] |
+ |
10 |
|
+ |
11 |
""" |
+ |
12 |
urlpatterns = [ |
5 |
13 |
url(r'^$', views.index, name='blog-index'), |
6 |
14 |
url(r'^feed.rss$', views.rss, name='blog-feed'), |
7 |
15 |
url(r'^(?P<language>(.){2})/(?P<post_slug>(.)+)$', views.post, name='blog-post-language'), |
8 |
16 |
url(r'^monthly-archive$', views.monthly_archive, name="monthly-archive"), |
9 |
17 |
url(r'^(?P<post_slug>(.)+)$', views.post, name='blog-post'), |
10 |
18 |
] |
11 |
19 |
|
12 |
- | |
+ |
20 |
views.py ¶
15 additions and 56 deletions.
View changes Hide changes
1 |
1 |
import subprocess |
2 |
2 |
import requests |
3 |
3 |
|
4 |
4 |
|
5 |
5 |
from django.utils.translation import ugettext as _ |
6 |
6 |
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. |
7 |
7 |
from django.http import HttpResponseRedirect, HttpResponse |
8 |
8 |
from django.urls import reverse |
9 |
9 |
from django.template import loader # This allows to actually load the template. |
10 |
10 |
from .models import * |
11 |
11 |
from .forms import CommentForm |
12 |
12 |
from django.core.exceptions import ObjectDoesNotExist |
13 |
13 |
from django.utils import translation |
14 |
14 |
|
15 |
15 |
GERMAN = "de" |
16 |
16 |
SPANISH = "es" |
17 |
17 |
FRENCH = "fr" |
18 |
18 |
DUTCH = "nl" |
19 |
19 |
ENGLISH = "en" |
20 |
20 |
|
21 |
21 |
footer_links = [ |
22 |
- | [_("Back to main page"), "/blog"], |
23 |
- | [_("Contact"), "mailto:maarten.vangeneugden@student.uhasselt.be"], |
24 |
- | ] |
25 |
- | footer_description = _("Maarten's personal blog, with sprinkles and a dollop of healthy bugs.") |
26 |
- | |
27 |
- | def org_to_html(file_path): |
28 |
- | """ Converts the given org formatted file to HTML. |
29 |
- | This function directly returns the resulting HTML code. This function uses |
30 |
- | the amazing Haskell library Pandoc to convert the file (and takes care |
31 |
- | of header id's and all that stuff). |
32 |
- | """ |
33 |
- | # FIXME: Remove hardcoded link to media. Replace with media tag! |
34 |
- | # XXX: The reason I'm first converting all occurences of .jpg][ and .png][ |
35 |
- | # to .jpgPANDOCBUG][ and .pngPANDOCBUG][, is because of a Pandoc bug that |
36 |
- | # removes the text links for images. It is afterwards converted back, no |
37 |
- | # worries. |
38 |
- | file = open("/srv/django/website/media/"+file_path, "r", encoding="utf-8") |
39 |
- | text = file.read() |
40 |
- | file.close() |
41 |
- | text = text.replace(".jpg][", ".jpgPANDOCBUG][") |
42 |
- | text = text.replace(".png][", ".pngPANDOCBUG][") |
43 |
- | file = open("/tmp/blog-file.org", "w", encoding="utf-8") |
44 |
- | file.write(text) |
45 |
- | file.close() |
46 |
- | html_text = subprocess.check_output(["pandoc", "--from=org", "--to=html","/tmp/blog-file.org"]) |
47 |
- | html_text = html_text.decode("utf-8").replace(".jpgPANDOCBUG", ".jpg") |
48 |
- | html_text = html_text.replace(".pngPANDOCBUG", ".png") |
49 |
- | return html_text |
50 |
- | |
51 |
22 |
def get_available_post_languages(post): |
52 |
23 |
""" Returns the language codes for which a blog post exists. This function |
53 |
24 |
always returns English (because that field mustn't be empty). |
54 |
25 |
So say a blog post has an English, Dutch and French version (which means |
55 |
26 |
english_file, french_file and dutch_file aren't empty), the function will return {"en", |
56 |
27 |
"fr", "nl"}. """ |
57 |
28 |
available_languages = {ENGLISH} |
58 |
29 |
if post.german_file != "": |
59 |
30 |
available_languages.add(GERMAN) |
60 |
31 |
if post.spanish_file != "": |
61 |
32 |
available_languages.add(SPANISH) |
62 |
33 |
if post.french_file != "": |
63 |
34 |
available_languages.add(FRENCH) |
64 |
35 |
if post.dutch_file != "": |
65 |
36 |
available_languages.add(DUTCH) |
66 |
37 |
return available_languages |
67 |
38 |
|
68 |
39 |
def index(request): |
69 |
40 |
template = "blog/index.djhtml" |
70 |
41 |
posts = Post.objects.all() |
71 |
42 |
language = translation.get_language() |
72 |
- | |
73 |
43 |
post_links = [] |
74 |
- | for post in posts: |
75 |
- | blog_file = post.text_file(language) |
76 |
- | blog_text = org_to_html(blog_file.name) |
77 |
- | # TODO: The link can possibly be reversed in the DTL using the title, which is actually |
78 |
- | # a cleaner way to do it. Investigate. |
79 |
- | link = reverse("blog-post-language", args=[language, post.slug(language)]) |
80 |
- | post_links.append([post.title(language), post.published, blog_text, link]) |
81 |
- | |
82 |
44 |
context = { |
83 |
45 |
'posts': post_links, |
84 |
- | 'materialDesign_color': "brown", |
85 |
- | 'materialDesign_accentColor': "blue", |
86 |
- | 'navbar_title': _("Notepad from a student"), |
+ |
46 |
'navbar_title': _("Notepad from a student"), |
87 |
47 |
'navbar_backArrow': True, |
88 |
48 |
'footer_links': footer_links, |
89 |
- | 'footer_description': footer_description, |
90 |
- | 'stylesheet_name': "blog", |
91 |
49 |
} |
92 |
50 |
if not request.session.get("feed-fab-introduction-seen", default=False): |
93 |
- | context['introduce_feed'] = True |
94 |
- | request.session['feed-fab-introduction-seen'] = True |
95 |
- | return render(request, template, context) |
96 |
51 |
|
97 |
52 |
def post(request, post_slug, language=None): |
98 |
- | if request.method == "POST": # Handling a reply if one is sent |
+ |
53 |
if request.method == "POST": # Handling a reply if one is sent |
99 |
54 |
form = CommentForm(request.POST) |
100 |
55 |
for post in Post.objects.all(): |
101 |
56 |
if post.slug(language) == post_slug: |
102 |
57 |
form.post = post |
103 |
58 |
break |
104 |
59 |
if form.is_valid(): |
105 |
60 |
new_comment = form.save(commit=False) |
106 |
61 |
for post in Post.objects.all(): |
107 |
62 |
if post.slug(language) == post_slug: |
108 |
63 |
new_comment.post = post |
109 |
64 |
new_comment.save() |
110 |
65 |
if language is not None: |
111 |
- | if translation.check_for_language(language): |
112 |
- | translation.activate(language) |
113 |
- | request.session[translation.LANGUAGE_SESSION_KEY] = language |
114 |
- | #return post(request, post_slug) |
115 |
- | else: |
116 |
- | language = translation.get_language() |
117 |
- | |
118 |
66 |
template = "blog/post.djhtml" |
119 |
67 |
posts = Post.objects.all() |
120 |
- | #comments = Comment.objects.filter(post |
+ |
68 |
comments = Comment.objects.filter(post=article.post) |
+ |
69 |
context = { |
+ |
70 |
'article': article, |
+ |
71 |
'navbar_title': article.post.title(), |
+ |
72 |
'title': article.post.title(), |
+ |
73 |
'navbar_title': article.post.title(), |
+ |
74 |
'navbar_backArrow': True, |
+ |
75 |
'stylesheet_name': "blog", |
+ |
76 |
#posts = Post.objects.all() |
+ |
77 |
#comments = Comment.objects.filter(post |
121 |
78 |
for post in posts: |
+ |
79 |
for post in posts: |
122 |
80 |
if post.slug(language) == post_slug: |
123 |
81 |
comments = Comment.objects.filter(post=post) |
124 |
82 |
form = CommentForm() |
125 |
83 |
post_file = post.text_file(language) |
126 |
84 |
post_text = org_to_html(post_file.name) |
127 |
85 |
context = { |
128 |
86 |
'comments': comments, |
129 |
87 |
'form' : form, |
130 |
88 |
'human_post_title': post.title(language), |
131 |
89 |
'materialDesign_color': "brown", |
132 |
90 |
'materialDesign_accentColor': "blue", |
133 |
91 |
'article': post_text, |
134 |
92 |
'title': post.title(language), |
135 |
93 |
'navbar_title': post.title(language), |
136 |
94 |
'navbar_backArrow': False, |
137 |
95 |
'post_slug': post_slug, |
138 |
96 |
'footer_links': footer_links, |
139 |
97 |
'footer_description': footer_description, |
140 |
98 |
'stylesheet_name': "blog", |
141 |
99 |
} |
142 |
100 |
|
143 |
101 |
# Getting all available article links |
144 |
102 |
available = get_available_post_languages(post) |
145 |
103 |
if ENGLISH in available: |
146 |
104 |
context['english_link'] = reverse("blog-post-language", args=[ENGLISH, post.slug(ENGLISH)]) |
147 |
105 |
if DUTCH in available: |
148 |
106 |
context['dutch_link'] = reverse("blog-post-language", args=[DUTCH, post.slug(DUTCH)]) |
149 |
107 |
|
150 |
108 |
if FRENCH in available: |
151 |
109 |
context['french_link'] = reverse("blog-post-language", args=[FRENCH, post.slug(FRENCH)]) |
152 |
110 |
|
153 |
111 |
if SPANISH in available: |
154 |
112 |
context['spanish_link'] = reverse("blog-post-language", args=[SPANISH, post.slug(SPANISH)]) |
155 |
113 |
|
156 |
114 |
if GERMAN in available: |
157 |
115 |
context['german_link'] = reverse("blog-post-language", args=[GERMAN, post.slug(GERMAN)]) |
158 |
116 |
|
+ |
117 |
|
159 |
118 |
return render(request, template, context) |
160 |
- | |
+ |
119 |
|
161 |
120 |
def rss(request): |
162 |
121 |
template = "blog/feed.rss" |
163 |
122 |
context = { |
164 |
123 |
'items': FeedItem.objects.all(), |
165 |
124 |
} |
166 |
125 |
return render(request, template, context, content_type="application/rss+xml") |
167 |
126 |
|
168 |
127 |
|
169 |
128 |
def monthly_archive(request): |
170 |
129 |
template = "blog/monthly_archive.djhtml" |
171 |
130 |
language = translation.get_language() |
172 |
131 |
|
173 |
132 |
file_2017 = org_to_html("blog/weekly/2017.org") |
174 |
133 |
file_2018 = org_to_html("blog/weekly/2018.org") |
175 |
134 |
file_2019 = org_to_html("blog/weekly/2019.org") |
176 |
135 |
|
177 |
136 |
|
178 |
137 |
|
179 |
138 |
context = { |
180 |
139 |
't2017': file_2017, |
181 |
140 |
't2018': file_2018, |
182 |
141 |
't2019': file_2019, |
183 |
142 |
'materialDesign_color': "brown", |
184 |
143 |
'materialDesign_accentColor': "blue", |
185 |
144 |
'navbar_title': _("Weekly-archief"), |
186 |
145 |
'navbar_backArrow': True, |
187 |
146 |
'footer_links': footer_links, |
188 |
147 |
'footer_description': footer_description, |
189 |
148 |
'stylesheet_name': "blog", |
190 |
149 |
} |
191 |
150 |
return render(request, template, context) |
192 |
151 |