A+ uses containers to support local development and to power the CI/CD pipeline. Therefore you need to have the following installed:
Note that you will need to have admin rights to install these correctly.
Alternatively, you can have comparable software which enable you to use the commands docker
, docker-compose
and drone
.
If you are going to use Django translations, you must install the GNU gettext utilities
-
Follow the installation steps of course-templates to get a template course running.
-
After the installation, you should have A+ running at http://localhost:8000.
-
There are four main user-roles in A+. These all have readymade accounts, which can be used during development:
username: root, password: root (Using A+ as a superuser) username: teacher, password: teacher (Using A+ as a teacher) username: student, password: student (Using A+ as a student) username: assistant, password: assistant (Using A+ as a course assistant)
-
You can manage the database, user accounts and the content of the presented template course at http://localhost:8000/admin. You can log in with the superuser-account:
username: root, password: root
-
You can stop the containers with
CTRL + C
.
In order to develop the code within A+, you will need to have this repository, and the aforementioned course-templates forked and/or cloned on your machine. They can be located in separate directories.
-
Fork this repository by clicking "Fork"-button at the top of the page.
-
Clone the forked repository to your machine with e.g.:
$ git clone [email protected]:your-github-username/a-plus.git a-plus
-
To see the changes you make into the code, you need to mount the A+ code in the
docker-compose.yml
file. The file can be found at the root of thecourse-templates
project. Mounting happens by adding the path to your cloned a-plus directory to thevolumes
-section of thedocker-compose.yml
:# ... services: # ... plus: image: apluslms/run-aplus-front volumes: - data:/data # mount development version to /srv/aplus, when write access is not required - /home/user/a-plus/:/srv/aplus/:ro # or to /src/aplus, when write access is required #- /home/user/a-plus/:/src/aplus/:ro ports: - "8000:8000" depends_on: - grader # ...
If you wish to use features which require write access, e.g. update translations, change the /srv/
to /src/
.
Using /srv/
will allow the Django process to reload the code without stopping the container, but features which require the write access are not possible.
-
Download and build all required support files by running the Drone pipeline, i.e., execute the following in the a-plus project root:
$ cd [A-PLUS PROJECT ROOT] $ drone exec
-
Run the code within the container:
$ cd [COURSE-TEMPLATES PROJECT ROOT] $ ./docker-up.sh
Note that if you wish to develop the code in other services related to A+ (e.g. mooc-grader, which handles grading of some exercises), you need to follow the instructions given for those services, for example:
-
Ensure you followed the steps above and have the a-plus code mounted within the
docker-compose.yml
file and the containers running with./docker-up.sh
. -
In order to see changes in styles when working on the SCSS files, open up another terminal and run the following commands in the a-plus project root:
$ cd [A-PLUS PROJECT ROOT] $ drone exec
After the
drone exec
command finishes its execution, you can start monitoring the changes of the sass compiler by executing the following command.$ cd [A-PLUS PROJECT ROOT] $ ./dev_assets_watch_sass.sh
The command will start a container, which will monitor Sass directories for changes and compile them to CSS files when changes are detected.
Note that if you have mounted the code to /src/
, you need to restart the servers for these updates to take effect.
-
After completing the changes and before committing them, run:
$ drone exec
-
Include the built production versions of JavaScript and CSS files (e.g.
css/main.css
andcss/main.css.map
) into the git commit with your changes.
The assets_src/
contains folders, which are npm packages and should install compiled files to assets/
as part of npm install
.
To manage these, you can use ./dev_assets_run_npm.sh
, which runs npm commands in a container.
For example, to update package.json
of the translate-js
, run ./dev_assets_run_npm.sh translate-js update
.
Some parts are still easier to do without containers, tests and translations are such. Therefore they are run and updated outside the containers, within a virtual environment.
- For Selenium tests:
- Firefox browser installed
- xvfb virtual framebuffer installed (
sudo apt-get install xvfb
oraptdcon --install xvfb
)
- python3 installed
- Python module venv (or virtualenv) installed
- For translations: gettext (
aptdcon --install gettext
orsudo apt-get install gettext
)
-
Create a virtual environment for a-plus and activate it:
$ python3 -m venv venv_aplus $ source venv_aplus/bin/activate
-
Install necessary requirements for development:
$ cd [A-PLUS PROJECT ROOT] $ pip3 install --upgrade pip setuptools wheel $ pip3 install -r requirements.txt
-
Create a
local_settings.py
-file ata-plus/local_settings.py
. There is an example filelocal_settings.example.py
, which you can use as a starting point. -
Ensure your
local_settings.py
contains at least the following lines:DEBUG = True BASE_URL = 'http://localhost:8000/'
-
Run the existing django migrations:
$ python3 manage.py migrate
-
You can now run the local version without a container:
$ python3 manage.py runserver
If you need to be able to login or use the admin-page while running the code without a container, you can create a superuser by following these instructions.
-
Run the unit tests:
$ python3 manage.py test
Selenium tests check end-to-end -functioning from a user-perspective. The tests will automatically open and direct Firefox browser windows. Currently, the tests depend on a Unix type shell and are run within a virtual environment created above.
-
Install the necessary requirements:
$ pip3 install -r requirements_testing.txt
-
Download the latest release of geckodriver (choose the appropriate tar.gz file based on your machine) from https://github.com/mozilla/geckodriver/releases and extract it.
-
Check the path to your extracted geckodriver and add it to PATH:
$ export PATH=$PATH:/path-to-your-extracted-geckodriver/
-
To setup the servers and run all the tests at one go:
$ selenium_test/run_servers_and_tests.sh
-
Alternatively you can run individual tests:
$ cd selenium_test/test/ $ ../run_servers.sh $ python3 login_test.py $ python3 home_page_test.py $ ../kill_servers.sh
Before creating a pull request, you need to ensure that the English strings and Finnish translations are up-to-date. Translations are handled with Django and are done outside the container. If you have added new strings with new string keys, make sure that the keys are in the correct format.
-
In order to update the files containing translations, run:
$ python3 manage.py makemessages --no-obsolete --add-location file --all
-
You can easily check the lines that were affected:
$ git diff locale/fi/LC_MESSAGES/django.po $ git diff locale/en/LC_MESSAGES/django.po
-
Add new English texts and Finnish translations for the lines you have created. If you wish to edit existing strings, alter the corresponding
msgstr
strings in the English and/or Finnish files. -
And compile the translations to ensure they are working:
$ python3 manage.py compilemessages
The final step can also be used to ensure that the translations work, when the code is run in a container and mounted to /srv/
.
JWT permission checks are disabled when aplus-auth's DISABLE_LOGIN_CHECKS
setting is true. This should be the case automatically when DEBUG
is true,
unless you have overridden the default aplus-auth library settings.
When JWT permission checks are enabled, you'll need to configure the auth
settings. See DEPLOYMENT for more information.
(RSA keys, APLUS_AUTH
, ALIAS_TO_PUBLIC_KEY
and URL_TO_ALIAS
)
For more information, see the aplus-auth settings.
All models in A+ that use multi-table inheritance
inherit from the ModelWithInheritance
model. This model's default manager is
ModelWithInheritanceManager
, which inherits from InheritanceManager
provided by the django-model-utils
library.
When constructing querysets using the InheritanceManager.select_subclasses
method, the queryset will always return objects as their actual subclasses,
a.k.a. their "leaf classes". For example, when querying for LearningObject
s,
the resulting objects will be of type CourseChapter
, BaseExercise
,
LTIExercise
, or whatever their actual subclasses are. Using standard Django
querysets (without InheritanceManager
), the resulting objects will all be of
type LearningObject
.
select_subclasses
is called in ModelWithInheritanceManager.get_queryset
,
ensuring that objects are almost always returned as their actual subclasses
without needing to call select_subclasses
in queries. There are two
exceptions to this rule:
-
Accessing a single related object, via
OneToOneField
orForeignKey
(e.g.Submission.exercise
), uses the related model's base manager (which is usually Django'sManager
class) instead of its default manager (which is accessed viaobjects
).To work around this limitation, there are two custom relationship field classes,
DefaultOneToOneField
andDefaultForeignKey
, which use the default manager, and thus ensure that the related object is always returned as its actual subclass. These custom field classes are currently used in all relationships that reference an inherited model.Discussion about this issue: jazzband/django-model-utils#11
-
select_related
will not fetch the related objects as their actual subclasses. In the following snippet, theexercise
variable will not be an instance of its actual subclass, which could be e.g.LTIExercise
.submissions = Submission.objects.select_related('exercise') for submission in submissions: exercise = submission.exercise
If you want to ensure the
exercise
is an instance of its actual subclass, omit theselect_related
call, or useprefetch_related
instead, or call theas_leaf_class
method on theexercise
.