Skip to content

Commit 7e35f20

Browse files
authored
Merge pull request #1194 from p2pu/2025-content
Add course content
2 parents f9f9b3a + eb12a21 commit 7e35f20

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+3017
-6
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ Dockerfile
1515
compose/
1616
places/data/
1717
docker.env
18+
gdoc/

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ tmp/*.png
1616
*.mo
1717
scripts/*
1818
docker.env
19-
compose/
19+
compose/
20+
token.pickle
21+
credentials.json

content/__init__.py

Whitespace-only changes.

content/db.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from django.db import models
2+
3+
4+
class Content(models.Model):
5+
6+
latest = models.ForeignKey('content.ContentVersion', models.CASCADE, related_name='+', null=True, blank=True)
7+
based_on = models.ForeignKey('content.Content', models.SET_NULL, related_name='derived_content', null=True, blank=True) #on_delete=SET_NULL might cause a problem here
8+
9+
10+
class ContentVersion(models.Model):
11+
12+
container = models.ForeignKey(Content, models.CASCADE, related_name='versions')
13+
title = models.CharField(max_length = 100)
14+
content = models.TextField()
15+
date = models.DateTimeField(auto_now_add=True)
16+
comment = models.CharField(max_length = 100)
17+
author_uri = models.CharField(max_length=256)

content/forms.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django import forms
2+
from content import utils
3+
4+
class ContentForm(forms.Form):
5+
title = forms.CharField(max_length=80)
6+
content = forms.CharField(widget=forms.Textarea, required=False)
7+
8+
def clean_content(self):
9+
return utils.clean_user_content(self.cleaned_data['content'])

content/migrations/0001_initial.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Generated by Django 4.2.11 on 2025-07-24 07:26
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
initial = True
10+
11+
dependencies = [
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='Content',
17+
fields=[
18+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('based_on', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='derived_content', to='content.content')),
20+
],
21+
),
22+
migrations.CreateModel(
23+
name='ContentVersion',
24+
fields=[
25+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26+
('title', models.CharField(max_length=100)),
27+
('content', models.TextField()),
28+
('date', models.DateTimeField(auto_now_add=True)),
29+
('comment', models.CharField(max_length=100)),
30+
('author_uri', models.CharField(max_length=256)),
31+
('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='content.content')),
32+
],
33+
),
34+
migrations.AddField(
35+
model_name='content',
36+
name='latest',
37+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='content.contentversion'),
38+
),
39+
]

content/migrations/__init__.py

Whitespace-only changes.

content/models.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import json
2+
3+
from content import db
4+
from django.utils.text import slugify
5+
from django.utils.translation import gettext as _
6+
7+
import logging
8+
log = logging.getLogger(__name__)
9+
10+
11+
def content_uri2id(content_uri):
12+
return content_uri.strip('/').split('/')[-1]
13+
14+
15+
def get_content(content_uri, fields=[]):
16+
content_id = content_uri2id(content_uri)
17+
try:
18+
wrapper_db = db.Content.objects.get(id=content_id)
19+
latest_db = wrapper_db.latest
20+
except Exception as e:
21+
#TODO
22+
log.debug(e)
23+
return None
24+
25+
content = {
26+
"id": wrapper_db.id,
27+
"uri": "/uri/content/{0}".format(wrapper_db.id),
28+
"title": latest_db.title,
29+
"content": latest_db.content,
30+
}
31+
if "history" in fields:
32+
content["history"] = []
33+
for version in wrapper_db.versions.sort_by("date"):
34+
content['history'] += {
35+
"date": version.date,
36+
"author_uri": version.author_uri,
37+
"title": version.title,
38+
"comment": version.comment,
39+
}
40+
41+
return content
42+
43+
44+
def create_content(title, content, author_uri):
45+
#TODO check all required properties
46+
container_db = db.Content()
47+
container_db.save()
48+
content_db = db.ContentVersion(
49+
container=container_db,
50+
title=title,
51+
content=content,
52+
author_uri = author_uri,
53+
)
54+
#TODO if "comment" in content:
55+
# content_db.comment = content["comment"]
56+
content_db.save()
57+
container_db.latest = content_db
58+
container_db.save()
59+
return get_content("/uri/content/{0}".format(container_db.id))
60+
61+
62+
def update_content( uri, title, content, author_uri ):
63+
content_id = content_uri2id(uri)
64+
try:
65+
wrapper_db = db.Content.objects.get(id=content_id)
66+
except Exception as e:
67+
#TODO
68+
log.debug(e)
69+
return None
70+
71+
content_db = db.ContentVersion(
72+
container=wrapper_db,
73+
title=title,
74+
content=content,
75+
author_uri = author_uri,
76+
)
77+
#TODO if "comment" in content:
78+
# content_db.comment = content["comment"]
79+
content_db.save()
80+
wrapper_db.latest = content_db
81+
wrapper_db.save()
82+
return get_content("/uri/content/{0}".format(wrapper_db.id))
83+
84+
85+
def clone_content(uri):
86+
content_id = content_uri2id(uri)
87+
try:
88+
original_db = db.Content.objects.get(id=content_id)
89+
except Exception as e:
90+
log.debug(e)
91+
raise
92+
93+
container_db = db.Content(based_on=original_db)
94+
container_db.save()
95+
96+
content_db = db.ContentVersion(
97+
container=container_db,
98+
title=original_db.latest.title,
99+
content=original_db.latest.content,
100+
author_uri=original_db.latest.author_uri,
101+
)
102+
content_db.save()
103+
container_db.latest = content_db
104+
container_db.save()
105+
106+
return get_content("/uri/content/{0}".format(container_db.id))

content/templatetags/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from django import template
2+
from django.template.defaultfilters import stringfilter
3+
from django.utils.safestring import mark_safe
4+
5+
import markdown
6+
import bleach
7+
#from html5lib.tokenizer import HTMLTokenizer
8+
import re
9+
10+
register = template.Library()
11+
12+
def add_target_blank(attrs, new=False):
13+
attrs[(None, 'target')] = '_blank'
14+
return attrs
15+
16+
@register.filter(is_safe=True)
17+
@stringfilter
18+
def convert_content( markdown_text ):
19+
html = markdown.markdown(markdown_text)#TODO , ['tables'])
20+
html = bleach.linkify(html,
21+
callbacks=bleach.linkifier.DEFAULT_CALLBACKS + [add_target_blank]
22+
) #TODO, tokenizer=HTMLTokenizer)
23+
regex = re.compile(r'<pre\>.*?</pre\>', re.IGNORECASE|re.DOTALL)
24+
f = lambda m: re.sub(r'&amp;', '&', m.group(0))
25+
html = regex.sub(f, html)
26+
return mark_safe(html)
27+
28+
convert_content.is_safe = True

0 commit comments

Comments
 (0)