Skip to content

Commit eb1dd6c

Browse files
committed
Merge branch 'release/3.13.0'
2 parents fb1d463 + 4aee57d commit eb1dd6c

File tree

51 files changed

+1893
-89
lines changed

Some content is hidden

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

51 files changed

+1893
-89
lines changed

.github/workflows/auto-merge-dependency-updates.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- name: Dependabot metadata
1515
id: metadata
16-
uses: dependabot/fetch-metadata@v1.3.4
16+
uses: dependabot/fetch-metadata@v1.3.5
1717
with:
1818
github-token: "${{ secrets.GITHUB_TOKEN }}"
1919
- name: Approve

.github/workflows/crowdin-actions.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ jobs:
1919
uses: actions/checkout@v3
2020

2121
- name: Upload or update source files to Crowdin
22-
uses: crowdin/github-action@1.5.0
22+
uses: crowdin/github-action@1.5.2
2323
with:
2424
upload_sources: true
2525

2626
- name: Download German translations
27-
uses: crowdin/github-action@1.5.0
27+
uses: crowdin/github-action@1.5.2
2828
with:
2929
upload_sources: false
3030
download_translations: true
@@ -42,7 +42,7 @@ jobs:
4242
config: crowdin.yaml
4343

4444
- name: Download Spanish translations
45-
uses: crowdin/github-action@1.5.0
45+
uses: crowdin/github-action@1.5.2
4646
with:
4747
upload_sources: false
4848
download_translations: true

.github/workflows/test-and-deploy.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ jobs:
1919
uses: actions/checkout@v3
2020
- name: Create Docker network
2121
run: docker network create uccser-development-stack
22+
# Required for the node service
23+
- name: Set DOCKER_UID variable
24+
run: echo "DOCKER_UID=$(echo $UID)" >> $GITHUB_ENV
2225
- name: Start systems
2326
run: docker compose -f docker-compose.local.yml up -d
2427
- name: Run Django system check
@@ -32,6 +35,9 @@ jobs:
3235
uses: actions/checkout@v3
3336
- name: Create Docker network
3437
run: docker network create uccser-development-stack
38+
# Required for the node service
39+
- name: Set DOCKER_UID variable
40+
run: echo "DOCKER_UID=$(echo $UID)" >> $GITHUB_ENV
3541
- name: Start systems
3642
run: docker compose -f docker-compose.local.yml up -d
3743
- name: Create static files
@@ -125,6 +131,9 @@ jobs:
125131

126132
- name: Create Docker network
127133
run: docker network create uccser-development-stack
134+
# Required for the node service
135+
- name: Set DOCKER_UID variable
136+
run: echo "DOCKER_UID=$(echo $UID)" >> $GITHUB_ENV
128137

129138
- name: Start system
130139
run: docker compose -f docker-compose.local.yml up -d
@@ -174,6 +183,10 @@ jobs:
174183
- name: Create Docker network
175184
run: docker network create uccser-development-stack
176185

186+
# Required for the node service
187+
- name: Set DOCKER_UID variable
188+
run: echo "DOCKER_UID=$(echo $UID)" >> $GITHUB_ENV
189+
177190
- name: Start system
178191
run: docker compose -f docker-compose.local.yml up -d
179192

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Shannon's experiment
2+
3+
It turns out that there are limits to how small we can compress a file, and to explore this we’re going to look at multi-million dollar frauds, and a fun game that exposes the limits of compression.
4+
5+
Every so often someone claims to have invented an amazing {glossary-link term="lossless"}lossless{glossary-link end} compression method that can compress *any* file, including compressed files. If that was true, it would mean that you could use the method to compress files down to just a few {glossary-link term="byte"}bytes{glossary-link end}. Any file could be downloaded in a fraction of a second, and computers could store billions of huge video files. It would revolutionise computing! But is there a limit on how small a file can be compressed?
6+
7+
{panel type="curiosity"}
8+
9+
# Fraud in data compression
10+
11+
A number of fake systems have been produced that claim to compress any file as small as you want.
12+
They have even been demonstrated! But they all turn out to be a fake system - the common trick is to have a “compression” program that actually just hides the file being compressed somewhere else on a computer, and replaces it with a tiny file. The decompression program copies the hidden file back. It’s a very simple program to write, and looks very impressive because files are replaced with tiny ones, and then reproduced exactly.
13+
14+
You can find a few examples of these if you search for “Pixelon”, “Adam’s platform”, “Near Zero”, or “Madison Priest” (add the terms “compression” and “fraud” if you are searching for these, as there are other legitimate organisations with similar names). Several of these organisations have taken millions of dollars from investors who didn’t understand the limits of compression, and all ended up failing.
15+
16+
{panel end}
17+
18+
## How small can we compress a file?
19+
20+
With {glossary-link term="lossy"}lossy{glossary-link end} compression, there isn’t a limit to how small you can compress a file, since it’s just a matter of giving up quality to make the file smaller. You could compress a 10-megapixel photo down to just one pixel (perhaps the average colour of the whole photo). It wouldn’t be much use to anyone, but technically it’s a lossy version of the original photo.
21+
22+
But with lossless compression, the original file needs to be able to be restored to exactly its original form.
23+
24+
We’ve seen that compression works by taking advantage of patterns in the data being compressed.
25+
In the 1950s an interesting experiment was developed by a scientist called Claude Shannon, in which he asked humans to predict English text, and he measured how good the compression would be using their ability to make predictions.
26+
The idea is that if a computer was as good at English as a human, then that might be near the limit of what is possible.
27+
28+
Shannon’s game is easy to play.
29+
Just click on the letter that you think is coming up next in the sentence (you’ll need to start by guessing the first letter). The number of guesses you make give an indication of how predictable the letter is.
30+
These guesses are used to estimate how small the data could be compressed -- you can see this estimate by clicking on the “Show statistics” button.
31+
The “bits per character” is the estimate of how many bits would be needed on average to represent each character.
32+
Plain English text is often stored in 7 or 8 bits for each character (using Unicode or ASCII), and you should find that using your predictions the experiment can do better than that, usually around 2 bits per character.
33+
That’s equivalent to compressing a normal file (8 bits per character) to a quarter of its size.
34+
35+
Try it here:
36+
37+
{interactive slug="shannon-experiment" type="whole-page" alt="Shannon's experiment"}
38+
39+
But it’s very hard to get smaller than 1 bit per character (one eighth of the normal size).
40+
Shannon found that this seems to be a limit for how much we can compress English text.
41+
And this is one reason that we should be suspicious of any system that claims to compress English text to much smaller than one eighth of its original size.
42+
43+
{panel type="teacher-note"}
44+
45+
# Creating your own experiment
46+
47+
This interactive contains an option to create your own experiment.
48+
For example, this could be used to tailor the sentence set to use words that are more familiar to your students.
49+
50+
Additionally we have support for multiple different languages and sentence sets.
51+
Currently, we have a sentence set for Te Reo Māori, and the original sentences used by Shannon in 1951.
52+
53+
If you would like to use another language with a different set of characters and/or accents, this also works!
54+
When creating a custom sentence, any characters that aren't already on the keyboard get added automatically.
55+
56+
Lastly, you could also considering using a pattern that is easily guessed once they realise what is happening, such as "AAAAAAAAAAAAAAAA", "ABABABABABAB", or "blah blah blah blah blah blah blah blah blah blah".
57+
These have very close to zero information content as they are very predictable.
58+
At the other extreme, a (fake) passowrd such as "P6dQKg#S58dw66p" could be used to explore how hard it is to guess random characters.
59+
60+
{panel end}
61+
62+
{comment - could add more about Shannon, model at sender and received, movie about him}

csfieldguide/chapters/content/structure/coding-compression/sections/sections.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ general-purpose:
1919
audio-compression:
2020
section-number: 7
2121

22-
the-whole-story:
22+
shannons-experiment:
2323
section-number: 8
2424

25-
further-reading:
25+
the-whole-story:
2626
section-number: 9
27+
28+
further-reading:
29+
section-number: 10

csfieldguide/chapters/management/commands/_ChapterSectionHeadingsLoader.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@ def load(self):
3939
4040
"""
4141
for language, content in self.content_translations.items():
42+
i = 0
4243
if content.heading_tree:
43-
for (i, heading_node) in enumerate(content.heading_tree):
44+
for heading_node in content.heading_tree:
4445
self.chapter_section.headings.update_or_create(
45-
slug=heading_node.title_slug,
46+
number=i,
47+
language=language,
4648
defaults={
4749
'name': heading_node.title,
48-
'language': language,
49-
'number': i,
50+
'slug': heading_node.title_slug
5051
}
5152
)
53+
i += 1
54+
self.chapter_section.headings.filter(number__gte=i, language=language).delete()

csfieldguide/chapters/management/commands/_ChapterSectionsLoader.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ def load(self):
3333
field.
3434
"""
3535
chapter_sections_structure = self.load_yaml_file(self.structure_file_path)
36-
section_numbers = []
36+
next_section_number = 1
3737

3838
for (section_slug, section_structure) in chapter_sections_structure.items():
39-
4039
if section_structure is None:
4140
raise MissingRequiredFieldError(
4241
self.structure_file_path,
@@ -57,8 +56,15 @@ def load(self):
5756
"section-number - value '{}' is invalid".format(section_number),
5857
"section-number must be an integer value."
5958
)
59+
if section_number != next_section_number:
60+
raise InvalidYAMLValueError(
61+
self.structure_file_path,
62+
"section-number - value '{}' is invalid".format(section_number),
63+
"section-numbers must be in sequential order. The next expected number was '{}'."
64+
.format(next_section_number)
65+
)
6066

61-
section_numbers.append(section_number)
67+
next_section_number += 1
6268

6369
chapter_section_translations = self.get_blank_translation_dictionary()
6470

@@ -69,9 +75,9 @@ def load(self):
6975
chapter_section_translations[language]["name"] = content.title
7076

7177
chapter_section, created = self.chapter.chapter_sections.update_or_create(
72-
slug=section_slug,
78+
number=section_number,
7379
defaults={
74-
'number': section_number,
80+
'slug': section_slug,
7581
'languages': list(content_translations.keys()),
7682
}
7783
)
@@ -101,11 +107,4 @@ def load(self):
101107
structure_filename=self.structure_file_path,
102108
).load()
103109

104-
# assumes first section number is always 1
105-
for counter, section_number in enumerate(section_numbers, 1):
106-
if section_number != counter:
107-
raise InvalidYAMLValueError(
108-
self.structure_file_path,
109-
"section-number - value '{}' is invalid".format(section_number),
110-
"section-numbers must be in sequential order. The next expected number was '{}'.".format(counter)
111-
)
110+
self.chapter.chapter_sections.filter(number__gte=next_section_number).delete()

csfieldguide/chapters/management/commands/_ChaptersLoader.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ def load(self):
6969

7070
# Create or update chapter object and save to the db
7171
chapter, created = Chapter.objects.update_or_create(
72-
slug=self.chapter_slug,
72+
number=self.chapter_number,
7373
defaults={
74-
'number': self.chapter_number,
74+
'slug': self.chapter_slug,
7575
'icon': chapter_icon,
7676
'video': video
7777
}

csfieldguide/chapters/management/commands/loadchapters.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os.path
44
from django.core.management.base import BaseCommand
55
from django.conf import settings
6+
from django.db import transaction
67
from utils.BaseLoader import BaseLoader
78
from utils.LoaderFactory import LoaderFactory
89
from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError
@@ -19,6 +20,7 @@ class Command(BaseCommand):
1920

2021
help = "Converts Markdown files listed in structure file and stores"
2122

23+
@transaction.atomic
2224
def handle(self, *args, **options):
2325
"""Automatically called when the loadchapters command is given."""
2426
factory = LoaderFactory()
@@ -51,6 +53,7 @@ def handle(self, *args, **options):
5153
"Application Structure"
5254
)
5355
else:
56+
next_chapter_number = 1
5457
for chapter_slug in chapters:
5558
chapter_structure_file = "{}.yaml".format(chapter_slug)
5659

@@ -67,6 +70,16 @@ def handle(self, *args, **options):
6770
"chapter-number - value '{}' is invalid".format(chapter_number),
6871
"chapter-number must be an integer value."
6972
)
73+
if chapter_number != next_chapter_number:
74+
raise InvalidYAMLValueError(
75+
structure_file_path,
76+
"chapter-number - value '{}' is invalid".format(chapter_number),
77+
("chapter-numbers must be in sequential order. The next expected number was '{}'."
78+
.format(next_chapter_number))
79+
)
80+
81+
next_chapter_number += 1
82+
7083
factory.create_chapter_loader(
7184
base_path=base_path,
7285
content_path=chapter_slug,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.16 on 2022-11-24 02:49
2+
3+
from django.db import migrations, models
4+
import django.db.models.constraints
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('chapters', '0036_alter_chaptersection_options'),
11+
]
12+
13+
operations = [
14+
migrations.AddConstraint(
15+
model_name='chapter',
16+
constraint=models.UniqueConstraint(deferrable=django.db.models.constraints.Deferrable['DEFERRED'], fields=('slug',), name='slug_deferred'),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.16 on 2022-11-24 03:18
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('chapters', '0037_chapter_slug_deferred'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='chapter',
15+
name='slug',
16+
field=models.SlugField(),
17+
),
18+
]

csfieldguide/chapters/models.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from django.db import models
44
from interactives.models import Interactive
5-
from django.core.exceptions import ValidationError
65
from django.utils.translation import gettext_lazy as _
76
from utils.TranslatableModel import TranslatableModel
87
from django.urls import reverse
@@ -54,7 +53,7 @@ class Chapter(TranslatableModel):
5453
"""Model for chapter in database."""
5554

5655
# Auto-incrementing 'id' field is automatically set by Django
57-
slug = models.SlugField(unique=True)
56+
slug = models.SlugField() # This is set unique in the Meta child class
5857
name = models.CharField(max_length=100, default="")
5958
number = models.SmallIntegerField(unique=True)
6059
introduction = models.TextField(default="")
@@ -104,6 +103,10 @@ class Meta:
104103
verbose_name = _("chapter")
105104
verbose_name_plural = _("chapters")
106105

106+
constraints = [
107+
models.UniqueConstraint(fields=["slug"], deferrable=models.Deferrable.DEFERRED, name="slug_deferred")
108+
]
109+
107110

108111
class ChapterSection(TranslatableModel):
109112
"""Model for each section in a chapter in database."""
@@ -128,24 +131,6 @@ def __str__(self):
128131
"""
129132
return self.name
130133

131-
def clean(self):
132-
"""Use to check for unique section numbers.
133-
134-
Raises:
135-
ValidationError: when the section being added uses
136-
an existing section number for this chapter.
137-
"""
138-
# get all sections with same section number and chapter as new section being added
139-
sections = ChapterSection.objects.filter(number=self.number, chapter=self.chapter)
140-
# if already exists section with same number in same chapter, then throw error!
141-
if len(sections) > 1:
142-
raise ValidationError(('Section number must be unique per chapter.'))
143-
144-
def save(self, *args, **kwargs):
145-
"""Override save method to validate unique section numbers."""
146-
super(ChapterSection, self).save(*args, **kwargs)
147-
self.clean()
148-
149134
class Meta:
150135
"""Set consistent ordering of chapter sections."""
151136

csfieldguide/config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Module for Django system configuration."""
22

3-
__version__ = "3.12.6"
3+
__version__ = "3.13.0"

0 commit comments

Comments
 (0)